Merge branch 'gtk3' into 'gtk3'
Port recent changes to OMEMO See merge request !16
This commit is contained in:
379
omemo/.pylintrc
379
omemo/.pylintrc
@@ -1,379 +0,0 @@
|
|||||||
[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
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
[style]
|
|
||||||
based_on_style = pep8
|
|
||||||
align_closing_bracket_with_visual_indent = true
|
|
||||||
join_multiple_lines = true
|
|
||||||
@@ -1,3 +1,28 @@
|
|||||||
|
1.0.1 / 2017-01-14
|
||||||
|
- Better XEP Compliance
|
||||||
|
- Bugfixes
|
||||||
|
|
||||||
|
1.0.0 / 2016-12-04
|
||||||
|
- Bugfixes
|
||||||
|
|
||||||
|
0.9.9 / 2016-12-01
|
||||||
|
- Bugfixes
|
||||||
|
|
||||||
|
0.9.8 / 2016-11-28
|
||||||
|
- Fix a Problem where OMEMO wouldnt activate after the plugin is updated
|
||||||
|
- Add QR Verification Code to Plugin Config
|
||||||
|
|
||||||
|
0.9.7 / 2016-11-12
|
||||||
|
- Bugfixes
|
||||||
|
|
||||||
|
0.9.6 / 2016-11-01
|
||||||
|
- Bugfixes
|
||||||
|
|
||||||
|
0.9.5 / 2016-10-10
|
||||||
|
- Add GroupChat BETA
|
||||||
|
- Add Option to delete Fingerprints
|
||||||
|
- Add Option to deactivate Accounts for OMEMO
|
||||||
|
|
||||||
0.9.0 / 2016-08-28
|
0.9.0 / 2016-08-28
|
||||||
- Send INFO message to resources who dont support OMEMO
|
- Send INFO message to resources who dont support OMEMO
|
||||||
- Check dependencys and give correct error message
|
- Check dependencys and give correct error message
|
||||||
|
|||||||
@@ -1,90 +0,0 @@
|
|||||||
# 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`
|
|
||||||
|
|
||||||
[](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)
|
|
||||||
@@ -1,883 +1 @@
|
|||||||
# -*- coding: utf-8 -*-
|
from .omemoplugin import OmemoPlugin
|
||||||
#
|
|
||||||
# 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)
|
|
||||||
|
|||||||
@@ -1,16 +1,22 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Generated with glade 3.18.3 -->
|
||||||
<interface>
|
<interface>
|
||||||
<requires lib="gtk+" version="2.16"/>
|
<requires lib="gtk+" version="3.0"/>
|
||||||
<!-- interface-naming-policy toplevel-contextual -->
|
|
||||||
<object class="GtkListStore" id="account_store">
|
<object class="GtkListStore" id="account_store">
|
||||||
<columns>
|
<columns>
|
||||||
<!-- column-name accountname -->
|
<!-- column-name accounts -->
|
||||||
<column type="gchararray"/>
|
<column type="gchararray"/>
|
||||||
</columns>
|
</columns>
|
||||||
</object>
|
</object>
|
||||||
<object class="GtkListStore" id="deviceid_store">
|
<object class="GtkListStore" id="deviceid_store">
|
||||||
<columns>
|
<columns>
|
||||||
<!-- column-name Device -->
|
<!-- column-name Device -->
|
||||||
|
<column type="gint"/>
|
||||||
|
</columns>
|
||||||
|
</object>
|
||||||
|
<object class="GtkListStore" id="disabled_account_store">
|
||||||
|
<columns>
|
||||||
|
<!-- column-name accounts -->
|
||||||
<column type="gchararray"/>
|
<column type="gchararray"/>
|
||||||
</columns>
|
</columns>
|
||||||
</object>
|
</object>
|
||||||
@@ -24,6 +30,8 @@
|
|||||||
<column type="gchararray"/>
|
<column type="gchararray"/>
|
||||||
<!-- column-name fingerprint -->
|
<!-- column-name fingerprint -->
|
||||||
<column type="gchararray"/>
|
<column type="gchararray"/>
|
||||||
|
<!-- column-name deviceid -->
|
||||||
|
<column type="gint"/>
|
||||||
</columns>
|
</columns>
|
||||||
</object>
|
</object>
|
||||||
<object class="GtkNotebook" id="notebook1">
|
<object class="GtkNotebook" id="notebook1">
|
||||||
@@ -88,8 +96,8 @@
|
|||||||
<property name="width_request">110</property>
|
<property name="width_request">110</property>
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
<property name="xalign">0</property>
|
|
||||||
<property name="label" translatable="yes" comments="Descriptive label">Own Fingerprint:</property>
|
<property name="label" translatable="yes" comments="Descriptive label">Own Fingerprint:</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
<attributes>
|
<attributes>
|
||||||
<attribute name="weight" value="bold"/>
|
<attribute name="weight" value="bold"/>
|
||||||
</attributes>
|
</attributes>
|
||||||
@@ -104,7 +112,6 @@
|
|||||||
<object class="GtkLabel" id="fingerprint_label">
|
<object class="GtkLabel" id="fingerprint_label">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
<property name="label"><tt>-------- -------- -------- -------- -------- </tt></property>
|
|
||||||
<property name="use_markup">True</property>
|
<property name="use_markup">True</property>
|
||||||
<property name="selectable">True</property>
|
<property name="selectable">True</property>
|
||||||
</object>
|
</object>
|
||||||
@@ -130,8 +137,8 @@
|
|||||||
<property name="width_request">110</property>
|
<property name="width_request">110</property>
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
<property name="xalign">0</property>
|
|
||||||
<property name="label" translatable="yes">Own Device ID:</property>
|
<property name="label" translatable="yes">Own Device ID:</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
<attributes>
|
<attributes>
|
||||||
<attribute name="weight" value="bold"/>
|
<attribute name="weight" value="bold"/>
|
||||||
</attributes>
|
</attributes>
|
||||||
@@ -147,7 +154,6 @@
|
|||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
<property name="xalign">0</property>
|
<property name="xalign">0</property>
|
||||||
<property name="label" translatable="yes">0</property>
|
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">False</property>
|
<property name="expand">False</property>
|
||||||
@@ -162,6 +168,37 @@
|
|||||||
<property name="position">2</property>
|
<property name="position">2</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkImage" id="qrcode">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
<property name="stock">gtk-missing-image</property>
|
||||||
|
<property name="icon_size">0</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">3</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="qrinfo">
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="no_show_all">True</property>
|
||||||
|
<property name="label" translatable="yes">For Verification QRCode please install python-qrcode</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
<attributes>
|
||||||
|
<attribute name="weight" value="bold"/>
|
||||||
|
<attribute name="foreground" value="#ffff00000000"/>
|
||||||
|
</attributes>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">4</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child type="tab">
|
<child type="tab">
|
||||||
@@ -185,8 +222,6 @@
|
|||||||
<property name="height_request">200</property>
|
<property name="height_request">200</property>
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">True</property>
|
<property name="can_focus">True</property>
|
||||||
<property name="hscrollbar_policy">automatic</property>
|
|
||||||
<property name="vscrollbar_policy">automatic</property>
|
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkTreeView" id="fingerprint_view">
|
<object class="GtkTreeView" id="fingerprint_view">
|
||||||
<property name="height_request">300</property>
|
<property name="height_request">300</property>
|
||||||
@@ -196,10 +231,16 @@
|
|||||||
<property name="search_column">0</property>
|
<property name="search_column">0</property>
|
||||||
<property name="tooltip_column">3</property>
|
<property name="tooltip_column">3</property>
|
||||||
<signal name="button-press-event" handler="fpr_button_pressed_cb" swapped="no"/>
|
<signal name="button-press-event" handler="fpr_button_pressed_cb" swapped="no"/>
|
||||||
|
<child internal-child="selection">
|
||||||
|
<object class="GtkTreeSelection" id="treeview-selection1"/>
|
||||||
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkTreeViewColumn" id="name_column">
|
<object class="GtkTreeViewColumn" id="name_column">
|
||||||
<property name="resizable">True</property>
|
<property name="resizable">True</property>
|
||||||
<property name="title">Name</property>
|
<property name="title">Name</property>
|
||||||
|
<property name="clickable">True</property>
|
||||||
|
<property name="sort_indicator">True</property>
|
||||||
|
<property name="sort_column_id">1</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkCellRendererText" id="cellrenderertext2"/>
|
<object class="GtkCellRendererText" id="cellrenderertext2"/>
|
||||||
<attributes>
|
<attributes>
|
||||||
@@ -261,6 +302,21 @@
|
|||||||
<property name="position">0</property>
|
<property name="position">0</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="delfprbutton">
|
||||||
|
<property name="label" translatable="yes">Delete 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="delfpr_button_clicked" swapped="no"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">False</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">False</property>
|
<property name="expand">False</property>
|
||||||
@@ -295,8 +351,8 @@
|
|||||||
<property name="height_request">25</property>
|
<property name="height_request">25</property>
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
<property name="xalign">0</property>
|
|
||||||
<property name="label" translatable="yes">Published Devices</property>
|
<property name="label" translatable="yes">Published Devices</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
<attributes>
|
<attributes>
|
||||||
<attribute name="style" value="normal"/>
|
<attribute name="style" value="normal"/>
|
||||||
<attribute name="weight" value="bold"/>
|
<attribute name="weight" value="bold"/>
|
||||||
@@ -314,13 +370,15 @@
|
|||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">True</property>
|
<property name="can_focus">True</property>
|
||||||
<property name="hscrollbar_policy">never</property>
|
<property name="hscrollbar_policy">never</property>
|
||||||
<property name="vscrollbar_policy">automatic</property>
|
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkTreeView" id="deviceid_view">
|
<object class="GtkTreeView" id="deviceid_view">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
<property name="model">deviceid_store</property>
|
<property name="model">deviceid_store</property>
|
||||||
<property name="search_column">0</property>
|
<property name="search_column">0</property>
|
||||||
|
<child internal-child="selection">
|
||||||
|
<object class="GtkTreeSelection" id="treeview-selection2"/>
|
||||||
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkTreeViewColumn" id="deviceid_column">
|
<object class="GtkTreeViewColumn" id="deviceid_column">
|
||||||
<property name="title" translatable="yes">Device ID</property>
|
<property name="title" translatable="yes">Device ID</property>
|
||||||
@@ -400,6 +458,172 @@
|
|||||||
<property name="tab_fill">False</property>
|
<property name="tab_fill">False</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkVBox" id="vbox2">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="label6">
|
||||||
|
<property name="height_request">30</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="label" translatable="yes">You have to restart Gajim for changes to take effect !</property>
|
||||||
|
<attributes>
|
||||||
|
<attribute name="weight" value="bold"/>
|
||||||
|
<attribute name="foreground" value="#ffff00000000"/>
|
||||||
|
</attributes>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkHBox" id="hbox6">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="border_width">12</property>
|
||||||
|
<property name="spacing">5</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkVBox" id="vbox5">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkScrolledWindow" id="scrolledwindow3">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkTreeView" id="active_accounts_view">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="model">account_store</property>
|
||||||
|
<child internal-child="selection">
|
||||||
|
<object class="GtkTreeSelection" id="treeview-selection3"/>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkTreeViewColumn" id="treeviewcolumn1">
|
||||||
|
<property name="title" translatable="yes">Active Accounts</property>
|
||||||
|
<property name="alignment">0.5</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkCellRendererText" id="cellrenderertext5"/>
|
||||||
|
<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">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="disable_accounts_btn">
|
||||||
|
<property name="label" translatable="yes">Disable Account</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<signal name="clicked" handler="disable_accounts_btn_clicked" 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">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkVBox" id="vbox6">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkScrolledWindow" id="scrolledwindow4">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkTreeView" id="disabled_accounts_view">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="model">disabled_account_store</property>
|
||||||
|
<child internal-child="selection">
|
||||||
|
<object class="GtkTreeSelection" id="treeview-selection4"/>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkTreeViewColumn" id="treeviewcolumn2">
|
||||||
|
<property name="title" translatable="yes">Disabled Accounts</property>
|
||||||
|
<property name="alignment">0.5</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkCellRendererText" id="cellrenderertext6"/>
|
||||||
|
<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">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="activate_accounts_btn">
|
||||||
|
<property name="label" translatable="yes">Activate Account</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<signal name="clicked" handler="activate_accounts_btn_clicked" 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">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="position">3</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child type="tab">
|
||||||
|
<object class="GtkLabel" id="disable_accounts">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="label" translatable="yes">Disable Accounts</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="position">3</property>
|
||||||
|
<property name="tab_fill">False</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
</object>
|
</object>
|
||||||
<object class="GtkMenu" id="fprclipboard_menu">
|
<object class="GtkMenu" id="fprclipboard_menu">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
|
|||||||
@@ -1,13 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Generated with glade 3.18.3 -->
|
||||||
<interface>
|
<interface>
|
||||||
<requires lib="gtk+" version="2.16"/>
|
<requires lib="gtk+" version="3.0"/>
|
||||||
<!-- 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">
|
<object class="GtkListStore" id="fingerprint_store">
|
||||||
<columns>
|
<columns>
|
||||||
<!-- column-name id -->
|
<!-- column-name id -->
|
||||||
@@ -18,6 +12,8 @@
|
|||||||
<column type="gchararray"/>
|
<column type="gchararray"/>
|
||||||
<!-- column-name fingerprint -->
|
<!-- column-name fingerprint -->
|
||||||
<column type="gchararray"/>
|
<column type="gchararray"/>
|
||||||
|
<!-- column-name deviceid -->
|
||||||
|
<column type="gint"/>
|
||||||
</columns>
|
</columns>
|
||||||
</object>
|
</object>
|
||||||
<object class="GtkNotebook" id="notebook1">
|
<object class="GtkNotebook" id="notebook1">
|
||||||
@@ -35,22 +31,28 @@
|
|||||||
<property name="height_request">200</property>
|
<property name="height_request">200</property>
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">True</property>
|
<property name="can_focus">True</property>
|
||||||
<property name="hscrollbar_policy">automatic</property>
|
|
||||||
<property name="vscrollbar_policy">automatic</property>
|
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkTreeView" id="fingerprint_view">
|
<object class="GtkTreeView" id="fingerprint_view">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">True</property>
|
<property name="can_focus">True</property>
|
||||||
|
<property name="has_tooltip">True</property>
|
||||||
<property name="model">fingerprint_store</property>
|
<property name="model">fingerprint_store</property>
|
||||||
|
<property name="headers_clickable">False</property>
|
||||||
<property name="search_column">0</property>
|
<property name="search_column">0</property>
|
||||||
<property name="tooltip_column">3</property>
|
<property name="tooltip_column">3</property>
|
||||||
<signal name="button-press-event" handler="fpr_button_pressed_cb" swapped="no"/>
|
<signal name="button-press-event" handler="fpr_button_pressed_cb" swapped="no"/>
|
||||||
|
<child internal-child="selection">
|
||||||
|
<object class="GtkTreeSelection" id="treeview-selection1"/>
|
||||||
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkTreeViewColumn" id="name_column">
|
<object class="GtkTreeViewColumn" id="name_column">
|
||||||
<property name="resizable">True</property>
|
<property name="resizable">True</property>
|
||||||
<property name="title">Name</property>
|
<property name="title">Name</property>
|
||||||
|
<property name="clickable">True</property>
|
||||||
|
<property name="sort_indicator">True</property>
|
||||||
|
<property name="sort_column_id">1</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkCellRendererText" id="cellrenderertext2"/>
|
<object class="GtkCellRendererText" id="NameCell"/>
|
||||||
<attributes>
|
<attributes>
|
||||||
<attribute name="text">1</attribute>
|
<attribute name="text">1</attribute>
|
||||||
</attributes>
|
</attributes>
|
||||||
@@ -62,7 +64,7 @@
|
|||||||
<property name="resizable">True</property>
|
<property name="resizable">True</property>
|
||||||
<property name="title">Trust</property>
|
<property name="title">Trust</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkCellRendererText" id="cellrenderertoggle1"/>
|
<object class="GtkCellRendererText" id="TrustCell"/>
|
||||||
<attributes>
|
<attributes>
|
||||||
<attribute name="text">2</attribute>
|
<attribute name="text">2</attribute>
|
||||||
</attributes>
|
</attributes>
|
||||||
@@ -74,7 +76,7 @@
|
|||||||
<property name="resizable">True</property>
|
<property name="resizable">True</property>
|
||||||
<property name="title">Fingerprint</property>
|
<property name="title">Fingerprint</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkCellRendererText" id="cellrenderertext4"/>
|
<object class="GtkCellRendererText" id="FingerprintCell"/>
|
||||||
<attributes>
|
<attributes>
|
||||||
<attribute name="markup">3</attribute>
|
<attribute name="markup">3</attribute>
|
||||||
</attributes>
|
</attributes>
|
||||||
@@ -181,8 +183,6 @@
|
|||||||
<property name="height_request">100</property>
|
<property name="height_request">100</property>
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">True</property>
|
<property name="can_focus">True</property>
|
||||||
<property name="hscrollbar_policy">automatic</property>
|
|
||||||
<property name="vscrollbar_policy">automatic</property>
|
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkTreeView" id="fingerprint_view_own">
|
<object class="GtkTreeView" id="fingerprint_view_own">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
@@ -193,12 +193,15 @@
|
|||||||
<property name="search_column">0</property>
|
<property name="search_column">0</property>
|
||||||
<property name="tooltip_column">3</property>
|
<property name="tooltip_column">3</property>
|
||||||
<signal name="button-press-event" handler="fpr_button_pressed_cb" swapped="no"/>
|
<signal name="button-press-event" handler="fpr_button_pressed_cb" swapped="no"/>
|
||||||
|
<child internal-child="selection">
|
||||||
|
<object class="GtkTreeSelection" id="treeview-selection2"/>
|
||||||
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkTreeViewColumn" id="name_column1">
|
<object class="GtkTreeViewColumn" id="name_column1">
|
||||||
<property name="resizable">True</property>
|
<property name="resizable">True</property>
|
||||||
<property name="title">Name</property>
|
<property name="title">Name</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkCellRendererText" id="cellrenderertext1"/>
|
<object class="GtkCellRendererText" id="NameCell1"/>
|
||||||
<attributes>
|
<attributes>
|
||||||
<attribute name="text">1</attribute>
|
<attribute name="text">1</attribute>
|
||||||
</attributes>
|
</attributes>
|
||||||
@@ -210,7 +213,7 @@
|
|||||||
<property name="resizable">True</property>
|
<property name="resizable">True</property>
|
||||||
<property name="title">Trust</property>
|
<property name="title">Trust</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkCellRendererText" id="cellrenderertoggle2"/>
|
<object class="GtkCellRendererText" id="TrustCell2"/>
|
||||||
<attributes>
|
<attributes>
|
||||||
<attribute name="text">2</attribute>
|
<attribute name="text">2</attribute>
|
||||||
</attributes>
|
</attributes>
|
||||||
@@ -222,7 +225,7 @@
|
|||||||
<property name="resizable">True</property>
|
<property name="resizable">True</property>
|
||||||
<property name="title">Fingerprint</property>
|
<property name="title">Fingerprint</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkCellRendererText" id="cellrenderertext3"/>
|
<object class="GtkCellRendererText" id="FingerprintCell1"/>
|
||||||
<attributes>
|
<attributes>
|
||||||
<attribute name="markup">3</attribute>
|
<attribute name="markup">3</attribute>
|
||||||
</attributes>
|
</attributes>
|
||||||
@@ -289,8 +292,6 @@
|
|||||||
<object class="GtkMenuItem" id="copyfprclipboard_item">
|
<object class="GtkMenuItem" id="copyfprclipboard_item">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">False</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"/>
|
<signal name="activate" handler="clipboard_button_cb" swapped="no"/>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
[info]
|
[info]
|
||||||
name: OMEMO
|
name: OMEMO
|
||||||
short_name: omemo
|
short_name: omemo
|
||||||
version: 0.9.0
|
version: 1.0.1
|
||||||
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.
|
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>
|
authors: Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de>
|
||||||
Daniel Gultsch <daniel@gultsch.de>
|
Daniel Gultsch <daniel@gultsch.de>
|
||||||
Philipp Hörist <philipp@hoerist.com>
|
Philipp Hörist <philipp@hoerist.com>
|
||||||
homepage: http://github.com/omemo/gajim-omemo.git
|
homepage: https://dev.gajim.org/gajim/gajim-plugins/wikis/OmemoGajimPlugin
|
||||||
min_gajim_version: 0.16.9
|
min_gajim_version: 0.16.9
|
||||||
max_gajim_version: 0.16.11
|
max_gajim_version: 0.16.11
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
|
import sys
|
||||||
import logging
|
import logging
|
||||||
log = logging.getLogger('gajim.plugin_system.omemo')
|
log = logging.getLogger('gajim.plugin_system.omemo')
|
||||||
try:
|
try:
|
||||||
@@ -35,7 +36,11 @@ def encrypt(key, iv, plaintext):
|
|||||||
|
|
||||||
|
|
||||||
def decrypt(key, iv, ciphertext):
|
def decrypt(key, iv, ciphertext):
|
||||||
return aes_decrypt(key, iv, ciphertext)
|
plaintext = aes_decrypt(key, iv, ciphertext).decode('utf-8')
|
||||||
|
if sys.version_info < (3, 0):
|
||||||
|
return unicode(plaintext)
|
||||||
|
else:
|
||||||
|
return plaintext
|
||||||
|
|
||||||
|
|
||||||
class NoValidSessions(Exception):
|
class NoValidSessions(Exception):
|
||||||
|
|||||||
@@ -29,11 +29,14 @@
|
|||||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
# 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.
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
import logging
|
||||||
from struct import pack, unpack
|
from struct import pack, unpack
|
||||||
|
|
||||||
from Crypto.Cipher import AES
|
from Crypto.Cipher import AES
|
||||||
from Crypto.Util import strxor
|
from Crypto.Util import strxor
|
||||||
|
|
||||||
|
log = logging.getLogger('gajim.plugin_system.omemo')
|
||||||
|
|
||||||
|
|
||||||
def gcm_rightshift(vec):
|
def gcm_rightshift(vec):
|
||||||
for x in range(15, 0, -1):
|
for x in range(15, 0, -1):
|
||||||
@@ -140,13 +143,20 @@ def gcm_encrypt(k, iv, plaintext, auth_data):
|
|||||||
|
|
||||||
def aes_encrypt(key, nonce, plaintext):
|
def aes_encrypt(key, nonce, plaintext):
|
||||||
""" Use AES128 GCM with the given key and iv to encrypt the payload. """
|
""" Use AES128 GCM with the given key and iv to encrypt the payload. """
|
||||||
c, t = gcm_encrypt(key, nonce, plaintext, '')
|
return gcm_encrypt(key, nonce, plaintext, '')
|
||||||
result = c + t
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
def aes_decrypt(_key, nonce, payload):
|
||||||
def aes_decrypt(key, nonce, payload):
|
|
||||||
""" Use AES128 GCM with the given key and iv to decrypt the payload. """
|
""" Use AES128 GCM with the given key and iv to decrypt the payload. """
|
||||||
|
if len(_key) >= 32:
|
||||||
|
# XEP-0384
|
||||||
|
log.debug('XEP Compliant Key/Tag')
|
||||||
|
ciphertext = payload
|
||||||
|
key = _key[:16]
|
||||||
|
mac = _key[16:]
|
||||||
|
else:
|
||||||
|
# Legacy
|
||||||
|
log.debug('Legacy Key/Tag')
|
||||||
ciphertext = payload[:-16]
|
ciphertext = payload[:-16]
|
||||||
|
key = _key
|
||||||
mac = payload[-16:]
|
mac = payload[-16:]
|
||||||
return gcm_decrypt(key, nonce, ciphertext, '', mac)
|
return gcm_decrypt(key, nonce, ciphertext, '', mac)
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import logging
|
||||||
from cryptography.hazmat.primitives.ciphers import Cipher
|
from cryptography.hazmat.primitives.ciphers import Cipher
|
||||||
from cryptography.hazmat.primitives.ciphers import algorithms
|
from cryptography.hazmat.primitives.ciphers import algorithms
|
||||||
from cryptography.hazmat.primitives.ciphers.modes import GCM
|
from cryptography.hazmat.primitives.ciphers.modes import GCM
|
||||||
@@ -32,10 +33,21 @@ if os.name == 'nt':
|
|||||||
else:
|
else:
|
||||||
from cryptography.hazmat.backends import default_backend
|
from cryptography.hazmat.backends import default_backend
|
||||||
|
|
||||||
|
log = logging.getLogger('gajim.plugin_system.omemo')
|
||||||
|
|
||||||
def aes_decrypt(key, iv, payload):
|
def aes_decrypt(_key, iv, payload):
|
||||||
""" Use AES128 GCM with the given key and iv to decrypt the payload. """
|
""" Use AES128 GCM with the given key and iv to decrypt the payload. """
|
||||||
|
if len(_key) >= 32:
|
||||||
|
# XEP-0384
|
||||||
|
log.debug('XEP Compliant Key/Tag')
|
||||||
|
data = payload
|
||||||
|
key = _key[:16]
|
||||||
|
tag = _key[16:]
|
||||||
|
else:
|
||||||
|
# Legacy
|
||||||
|
log.debug('Legacy Key/Tag')
|
||||||
data = payload[:-16]
|
data = payload[:-16]
|
||||||
|
key = _key
|
||||||
tag = payload[-16:]
|
tag = payload[-16:]
|
||||||
if os.name == 'nt':
|
if os.name == 'nt':
|
||||||
_backend = backend
|
_backend = backend
|
||||||
@@ -58,4 +70,4 @@ def aes_encrypt(key, iv, plaintext):
|
|||||||
algorithms.AES(key),
|
algorithms.AES(key),
|
||||||
GCM(iv),
|
GCM(iv),
|
||||||
backend=_backend).encryptor()
|
backend=_backend).encryptor()
|
||||||
return encryptor.update(plaintext) + encryptor.finalize() + encryptor.tag
|
return encryptor.update(plaintext) + encryptor.finalize(), encryptor.tag
|
||||||
|
|||||||
@@ -83,10 +83,16 @@ class LiteAxolotlStore(AxolotlStore):
|
|||||||
def saveIdentity(self, recepientId, identityKey):
|
def saveIdentity(self, recepientId, identityKey):
|
||||||
self.identityKeyStore.saveIdentity(recepientId, identityKey)
|
self.identityKeyStore.saveIdentity(recepientId, identityKey)
|
||||||
|
|
||||||
|
def deleteIdentity(self, recipientId, identityKey):
|
||||||
|
self.identityKeyStore.deleteIdentity(recipientId, identityKey)
|
||||||
|
|
||||||
def isTrustedIdentity(self, recepientId, identityKey):
|
def isTrustedIdentity(self, recepientId, identityKey):
|
||||||
return self.identityKeyStore.isTrustedIdentity(recepientId,
|
return self.identityKeyStore.isTrustedIdentity(recepientId,
|
||||||
identityKey)
|
identityKey)
|
||||||
|
|
||||||
|
def setTrust(self, identityKey, trust):
|
||||||
|
return self.identityKeyStore.setTrust(identityKey, trust)
|
||||||
|
|
||||||
def getTrustedFingerprints(self, jid):
|
def getTrustedFingerprints(self, jid):
|
||||||
return self.identityKeyStore.getTrustedFingerprints(jid)
|
return self.identityKeyStore.getTrustedFingerprints(jid)
|
||||||
|
|
||||||
@@ -127,6 +133,9 @@ class LiteAxolotlStore(AxolotlStore):
|
|||||||
# TODO Reuse this
|
# TODO Reuse this
|
||||||
return self.sessionStore.getSubDeviceSessions(recepientId)
|
return self.sessionStore.getSubDeviceSessions(recepientId)
|
||||||
|
|
||||||
|
def getJidFromDevice(self, device_id):
|
||||||
|
return self.sessionStore.getJidFromDevice(device_id)
|
||||||
|
|
||||||
def storeSession(self, recepientId, deviceId, sessionRecord):
|
def storeSession(self, recepientId, deviceId, sessionRecord):
|
||||||
self.sessionStore.storeSession(recepientId, deviceId, sessionRecord)
|
self.sessionStore.storeSession(recepientId, deviceId, sessionRecord)
|
||||||
|
|
||||||
@@ -139,6 +148,15 @@ class LiteAxolotlStore(AxolotlStore):
|
|||||||
def deleteAllSessions(self, recepientId):
|
def deleteAllSessions(self, recepientId):
|
||||||
self.sessionStore.deleteAllSessions(recepientId)
|
self.sessionStore.deleteAllSessions(recepientId)
|
||||||
|
|
||||||
|
def getSessionsFromJid(self, recipientId):
|
||||||
|
return self.sessionStore.getSessionsFromJid(recipientId)
|
||||||
|
|
||||||
|
def getSessionsFromJids(self, recipientId):
|
||||||
|
return self.sessionStore.getSessionsFromJids(recipientId)
|
||||||
|
|
||||||
|
def getAllSessions(self):
|
||||||
|
return self.sessionStore.getAllSessions()
|
||||||
|
|
||||||
def loadSignedPreKey(self, signedPreKeyId):
|
def loadSignedPreKey(self, signedPreKeyId):
|
||||||
return self.signedPreKeyStore.loadSignedPreKey(signedPreKeyId)
|
return self.signedPreKeyStore.loadSignedPreKey(signedPreKeyId)
|
||||||
|
|
||||||
|
|||||||
@@ -86,6 +86,13 @@ class LiteIdentityKeyStore(IdentityKeyStore):
|
|||||||
|
|
||||||
return result is not None
|
return result is not None
|
||||||
|
|
||||||
|
def deleteIdentity(self, recipientId, identityKey):
|
||||||
|
q = "DELETE FROM identities WHERE recipient_id = ? AND public_key = ?"
|
||||||
|
c = self.dbConn.cursor()
|
||||||
|
c.execute(q, (recipientId,
|
||||||
|
identityKey.getPublicKey().serialize()))
|
||||||
|
self.dbConn.commit()
|
||||||
|
|
||||||
def isTrustedIdentity(self, recipientId, identityKey):
|
def isTrustedIdentity(self, recipientId, identityKey):
|
||||||
q = "SELECT trust FROM identities WHERE recipient_id = ? " \
|
q = "SELECT trust FROM identities WHERE recipient_id = ? " \
|
||||||
"AND public_key = ?"
|
"AND public_key = ?"
|
||||||
@@ -160,8 +167,8 @@ class LiteIdentityKeyStore(IdentityKeyStore):
|
|||||||
c.execute(q, fingerprints)
|
c.execute(q, fingerprints)
|
||||||
self.dbConn.commit()
|
self.dbConn.commit()
|
||||||
|
|
||||||
def setTrust(self, _id, trust):
|
def setTrust(self, identityKey, trust):
|
||||||
q = "UPDATE identities SET trust = ? WHERE _id = ?"
|
q = "UPDATE identities SET trust = ? WHERE public_key = ?"
|
||||||
c = self.dbConn.cursor()
|
c = self.dbConn.cursor()
|
||||||
c.execute(q, (trust, _id))
|
c.execute(q, (trust, identityKey.getPublicKey().serialize()))
|
||||||
self.dbConn.commit()
|
self.dbConn.commit()
|
||||||
|
|||||||
@@ -48,6 +48,14 @@ class LiteSessionStore(SessionStore):
|
|||||||
deviceIds = [r[0] for r in result]
|
deviceIds = [r[0] for r in result]
|
||||||
return deviceIds
|
return deviceIds
|
||||||
|
|
||||||
|
def getJidFromDevice(self, device_id):
|
||||||
|
q = "SELECT recipient_id from sessions WHERE device_id = ?"
|
||||||
|
c = self.dbConn.cursor()
|
||||||
|
c.execute(q, (device_id, ))
|
||||||
|
result = c.fetchone()
|
||||||
|
|
||||||
|
return result[0].decode('utf-8')
|
||||||
|
|
||||||
def getActiveDeviceTuples(self):
|
def getActiveDeviceTuples(self):
|
||||||
q = "SELECT recipient_id, device_id FROM sessions WHERE active = 1"
|
q = "SELECT recipient_id, device_id FROM sessions WHERE active = 1"
|
||||||
c = self.dbConn.cursor()
|
c = self.dbConn.cursor()
|
||||||
@@ -82,6 +90,33 @@ class LiteSessionStore(SessionStore):
|
|||||||
self.dbConn.cursor().execute(q, (recipientId, ))
|
self.dbConn.cursor().execute(q, (recipientId, ))
|
||||||
self.dbConn.commit()
|
self.dbConn.commit()
|
||||||
|
|
||||||
|
def getAllSessions(self):
|
||||||
|
q = "SELECT _id, recipient_id, device_id, record, active from sessions"
|
||||||
|
c = self.dbConn.cursor()
|
||||||
|
result = []
|
||||||
|
for row in c.execute(q):
|
||||||
|
result.append((row[0], row[1].decode('utf-8'), row[2], row[3], row[4]))
|
||||||
|
return result
|
||||||
|
|
||||||
|
def getSessionsFromJid(self, recipientId):
|
||||||
|
q = "SELECT _id, recipient_id, device_id, record, active from sessions" \
|
||||||
|
" WHERE recipient_id = ?"
|
||||||
|
c = self.dbConn.cursor()
|
||||||
|
result = []
|
||||||
|
for row in c.execute(q, (recipientId,)):
|
||||||
|
result.append((row[0], row[1].decode('utf-8'), row[2], row[3], row[4]))
|
||||||
|
return result
|
||||||
|
|
||||||
|
def getSessionsFromJids(self, recipientId):
|
||||||
|
q = "SELECT _id, recipient_id, device_id, record, active from sessions" \
|
||||||
|
" WHERE recipient_id IN ({})" \
|
||||||
|
.format(', '.join(['?'] * len(recipientId)))
|
||||||
|
c = self.dbConn.cursor()
|
||||||
|
result = []
|
||||||
|
for row in c.execute(q, recipientId):
|
||||||
|
result.append((row[0], row[1].decode('utf-8'), row[2], row[3], row[4]))
|
||||||
|
return result
|
||||||
|
|
||||||
def setActiveState(self, deviceList, jid):
|
def setActiveState(self, deviceList, jid):
|
||||||
c = self.dbConn.cursor()
|
c = self.dbConn.cursor()
|
||||||
|
|
||||||
@@ -96,28 +131,6 @@ class LiteSessionStore(SessionStore):
|
|||||||
c.execute(q, deviceList)
|
c.execute(q, deviceList)
|
||||||
self.dbConn.commit()
|
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):
|
def getInactiveSessionsKeys(self, recipientId):
|
||||||
q = "SELECT record FROM sessions WHERE active = 0 AND recipient_id = ?"
|
q = "SELECT record FROM sessions WHERE active = 0 AND recipient_id = ?"
|
||||||
c = self.dbConn.cursor()
|
c = self.dbConn.cursor()
|
||||||
|
|||||||
@@ -29,6 +29,14 @@ class SQLDatabase():
|
|||||||
self.dbConn = dbConn
|
self.dbConn = dbConn
|
||||||
self.createDb()
|
self.createDb()
|
||||||
self.migrateDb()
|
self.migrateDb()
|
||||||
|
c = self.dbConn.cursor()
|
||||||
|
c.execute("PRAGMA synchronous=NORMAL;")
|
||||||
|
c.execute("PRAGMA journal_mode;")
|
||||||
|
mode = c.fetchone()[0]
|
||||||
|
# WAL is a persistent DB mode, dont override it if user has set it
|
||||||
|
if mode != 'wal':
|
||||||
|
c.execute("PRAGMA journal_mode=MEMORY;")
|
||||||
|
self.dbConn.commit()
|
||||||
|
|
||||||
def createDb(self):
|
def createDb(self):
|
||||||
if user_version(self.dbConn) == 0:
|
if user_version(self.dbConn) == 0:
|
||||||
|
|||||||
@@ -200,8 +200,8 @@ class OmemoState:
|
|||||||
key = self.handleWhisperMessage(sender_jid, sid, encrypted_key)
|
key = self.handleWhisperMessage(sender_jid, sid, encrypted_key)
|
||||||
except (NoSessionException, InvalidMessageException) as e:
|
except (NoSessionException, InvalidMessageException) as e:
|
||||||
log.warning('No Session found ' + e.message)
|
log.warning('No Session found ' + e.message)
|
||||||
log.warning('sender_jid => ' + str(sender_jid) +
|
log.warning('sender_jid => ' + str(sender_jid) + ' sid =>' +
|
||||||
' sid =>' + sid)
|
str(sid))
|
||||||
return
|
return
|
||||||
except (DuplicateMessageException) as e:
|
except (DuplicateMessageException) as e:
|
||||||
log.warning('Duplicate message found ' + str(e.args))
|
log.warning('Duplicate message found ' + str(e.args))
|
||||||
@@ -211,7 +211,7 @@ class OmemoState:
|
|||||||
log.warning('Duplicate message found ' + str(e.args))
|
log.warning('Duplicate message found ' + str(e.args))
|
||||||
return
|
return
|
||||||
|
|
||||||
result = decrypt(key, iv, payload).decode('utf-8')
|
result = decrypt(key, iv, payload)
|
||||||
|
|
||||||
log.debug("Decrypted Message => " + result)
|
log.debug("Decrypted Message => " + result)
|
||||||
return result
|
return result
|
||||||
@@ -226,43 +226,44 @@ class OmemoState:
|
|||||||
log.error('No known devices')
|
log.error('No known devices')
|
||||||
return
|
return
|
||||||
|
|
||||||
for dev in devices_list:
|
payload, tag = encrypt(key, iv, plaintext)
|
||||||
self.get_session_cipher(jid, dev)
|
|
||||||
session_ciphers = self.session_ciphers[jid]
|
# for XEP-384 Compliance uncomment
|
||||||
if not session_ciphers:
|
# key += tag
|
||||||
log.warning('No session ciphers for ' + jid)
|
payload += tag
|
||||||
return
|
|
||||||
|
|
||||||
# Encrypt the message key with for each of receivers devices
|
# Encrypt the message key with for each of receivers devices
|
||||||
for rid, cipher in session_ciphers.items():
|
for device in devices_list:
|
||||||
try:
|
try:
|
||||||
if self.isTrusted(cipher) == TRUSTED:
|
if self.isTrusted(jid, device) == TRUSTED:
|
||||||
encrypted_keys[rid] = cipher.encrypt(key).serialize()
|
cipher = self.get_session_cipher(jid, device)
|
||||||
|
cipher_key = cipher.encrypt(key)
|
||||||
|
prekey = isinstance(cipher_key, PreKeyWhisperMessage)
|
||||||
|
encrypted_keys[device] = (cipher_key.serialize(), prekey)
|
||||||
else:
|
else:
|
||||||
log.debug('Skipped Device because Trust is: ' +
|
log.debug('Skipped Device because Trust is: ' +
|
||||||
str(self.isTrusted(cipher)))
|
str(self.isTrusted(jid, device)))
|
||||||
except:
|
except:
|
||||||
log.warning('Failed to find key for device ' + str(rid))
|
log.warning('Failed to find key for device ' + str(device))
|
||||||
|
|
||||||
if len(encrypted_keys) == 0:
|
if len(encrypted_keys) == 0:
|
||||||
log_msg = 'Encrypted keys empty'
|
log.error('Encrypted keys empty')
|
||||||
log.error(log_msg)
|
raise NoValidSessions('Encrypted keys empty')
|
||||||
raise NoValidSessions(log_msg)
|
|
||||||
|
|
||||||
my_other_devices = set(self.own_devices) - set({self.own_device_id})
|
my_other_devices = set(self.own_devices) - set({self.own_device_id})
|
||||||
# Encrypt the message key with for each of our own devices
|
# Encrypt the message key with for each of our own devices
|
||||||
for dev in my_other_devices:
|
for device in my_other_devices:
|
||||||
try:
|
try:
|
||||||
cipher = self.get_session_cipher(from_jid, dev)
|
if self.isTrusted(from_jid, device) == TRUSTED:
|
||||||
if self.isTrusted(cipher) == TRUSTED:
|
cipher = self.get_session_cipher(from_jid, device)
|
||||||
encrypted_keys[dev] = cipher.encrypt(key).serialize()
|
cipher_key = cipher.encrypt(key)
|
||||||
|
prekey = isinstance(cipher_key, PreKeyWhisperMessage)
|
||||||
|
encrypted_keys[device] = (cipher_key.serialize(), prekey)
|
||||||
else:
|
else:
|
||||||
log.debug('Skipped own Device because Trust is: ' +
|
log.debug('Skipped own Device because Trust is: ' +
|
||||||
str(self.isTrusted(cipher)))
|
str(self.isTrusted(from_jid, device)))
|
||||||
except:
|
except:
|
||||||
log.warning('Failed to find key for device ' + str(dev))
|
log.warning('Failed to find key for device ' + str(device))
|
||||||
|
|
||||||
payload = encrypt(key, iv, plaintext)
|
|
||||||
|
|
||||||
result = {'sid': self.own_device_id,
|
result = {'sid': self.own_device_id,
|
||||||
'keys': encrypted_keys,
|
'keys': encrypted_keys,
|
||||||
@@ -273,14 +274,109 @@ class OmemoState:
|
|||||||
log.debug('Finished encrypting message')
|
log.debug('Finished encrypting message')
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def isTrusted(self, cipher):
|
def create_gc_msg(self, from_jid, jid, plaintext):
|
||||||
self.cipher = cipher
|
key = get_random_bytes(16)
|
||||||
self.state = self.cipher.sessionStore. \
|
iv = get_random_bytes(16)
|
||||||
loadSession(self.cipher.recipientId, self.cipher.deviceId). \
|
encrypted_keys = {}
|
||||||
getSessionState()
|
room = jid
|
||||||
self.key = self.state.getRemoteIdentityKey()
|
encrypted_jids = []
|
||||||
return self.store.identityKeyStore. \
|
|
||||||
isTrustedIdentity(self.cipher.recipientId, self.key)
|
devices_list = self.device_list_for(jid, True)
|
||||||
|
|
||||||
|
if len(devices_list) == 0:
|
||||||
|
log.error('No known devices')
|
||||||
|
return
|
||||||
|
|
||||||
|
payload, tag = encrypt(key, iv, plaintext)
|
||||||
|
|
||||||
|
# for XEP-384 Compliance uncomment
|
||||||
|
# key += tag
|
||||||
|
payload += tag
|
||||||
|
|
||||||
|
for tup in devices_list:
|
||||||
|
self.get_session_cipher(tup[0], tup[1])
|
||||||
|
|
||||||
|
# Encrypt the message key with for each of receivers devices
|
||||||
|
for nick in self.plugin.groupchat[room]:
|
||||||
|
jid_to = self.plugin.groupchat[room][nick]
|
||||||
|
if jid_to == self.own_jid:
|
||||||
|
continue
|
||||||
|
if jid_to in encrypted_jids: # We already encrypted to this JID
|
||||||
|
continue
|
||||||
|
for rid, cipher in self.session_ciphers[jid_to].items():
|
||||||
|
try:
|
||||||
|
if self.isTrusted(jid_to, rid) == TRUSTED:
|
||||||
|
cipher_key = cipher.encrypt(key)
|
||||||
|
prekey = isinstance(cipher_key, PreKeyWhisperMessage)
|
||||||
|
encrypted_keys[rid] = (cipher_key.serialize(), prekey)
|
||||||
|
else:
|
||||||
|
log.debug('Skipped Device because Trust is: ' +
|
||||||
|
str(self.isTrusted(jid_to, rid)))
|
||||||
|
except:
|
||||||
|
log.exception('ERROR:')
|
||||||
|
log.warning('Failed to find key for device ' +
|
||||||
|
str(rid))
|
||||||
|
encrypted_jids.append(jid_to)
|
||||||
|
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(from_jid, dev) == TRUSTED:
|
||||||
|
cipher_key = cipher.encrypt(key)
|
||||||
|
prekey = isinstance(cipher_key, PreKeyWhisperMessage)
|
||||||
|
encrypted_keys[dev] = (cipher_key.serialize(), prekey)
|
||||||
|
else:
|
||||||
|
log.debug('Skipped own Device because Trust is: ' +
|
||||||
|
str(self.isTrusted(from_jid, dev)))
|
||||||
|
except:
|
||||||
|
log.exception('ERROR:')
|
||||||
|
log.warning('Failed to find key for device ' + str(dev))
|
||||||
|
|
||||||
|
result = {'sid': self.own_device_id,
|
||||||
|
'keys': encrypted_keys,
|
||||||
|
'jid': jid,
|
||||||
|
'iv': iv,
|
||||||
|
'payload': payload}
|
||||||
|
|
||||||
|
log.debug('Finished encrypting message')
|
||||||
|
return result
|
||||||
|
|
||||||
|
def device_list_for(self, jid, gc=False):
|
||||||
|
""" Return a list of known device ids for the specified jid.
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
jid : string
|
||||||
|
The contacts jid
|
||||||
|
gc : bool
|
||||||
|
Groupchat Message
|
||||||
|
"""
|
||||||
|
if gc:
|
||||||
|
room = jid
|
||||||
|
devicelist = []
|
||||||
|
for nick in self.plugin.groupchat[room]:
|
||||||
|
jid_to = self.plugin.groupchat[room][nick]
|
||||||
|
if jid_to == self.own_jid:
|
||||||
|
continue
|
||||||
|
for device in self.device_ids[jid_to]:
|
||||||
|
devicelist.append((jid_to, device))
|
||||||
|
return devicelist
|
||||||
|
|
||||||
|
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 isTrusted(self, recipient_id, device_id):
|
||||||
|
record = self.store.loadSession(recipient_id, device_id)
|
||||||
|
identity_key = record.getSessionState().getRemoteIdentityKey()
|
||||||
|
return self.store.isTrustedIdentity(recipient_id, identity_key)
|
||||||
|
|
||||||
def getTrustedFingerprints(self, recipient_id):
|
def getTrustedFingerprints(self, recipient_id):
|
||||||
inactive = self.store.getInactiveSessionsKeys(recipient_id)
|
inactive = self.store.getInactiveSessionsKeys(recipient_id)
|
||||||
@@ -296,20 +392,6 @@ class OmemoState:
|
|||||||
|
|
||||||
return undecided
|
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):
|
def devices_without_sessions(self, jid):
|
||||||
""" List device_ids for the given jid which have no axolotl session.
|
""" List device_ids for the given jid which have no axolotl session.
|
||||||
|
|
||||||
@@ -364,10 +446,10 @@ class OmemoState:
|
|||||||
|
|
||||||
def handleWhisperMessage(self, recipient_id, device_id, key):
|
def handleWhisperMessage(self, recipient_id, device_id, key):
|
||||||
whisperMessage = WhisperMessage(serialized=key)
|
whisperMessage = WhisperMessage(serialized=key)
|
||||||
sessionCipher = self.get_session_cipher(recipient_id, device_id)
|
|
||||||
log.debug(self.account + " => Received WhisperMessage from " +
|
log.debug(self.account + " => Received WhisperMessage from " +
|
||||||
recipient_id)
|
recipient_id)
|
||||||
if self.isTrusted(sessionCipher) >= TRUSTED:
|
if self.isTrusted(recipient_id, device_id):
|
||||||
|
sessionCipher = self.get_session_cipher(recipient_id, device_id)
|
||||||
key = sessionCipher.decryptMsg(whisperMessage, textMsg=False)
|
key = sessionCipher.decryptMsg(whisperMessage, textMsg=False)
|
||||||
return key
|
return key
|
||||||
else:
|
else:
|
||||||
|
|||||||
1135
omemo/omemoplugin.py
Normal file
1135
omemo/omemoplugin.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,24 +0,0 @@
|
|||||||
# 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:
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
[flake8]
|
|
||||||
max-line-length=80
|
|
||||||
569
omemo/ui.py
569
omemo/ui.py
@@ -20,18 +20,30 @@
|
|||||||
|
|
||||||
import binascii
|
import binascii
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
import message_control
|
||||||
|
|
||||||
from gi.repository import GObject
|
from gi.repository import GObject, Gtk, GdkPixbuf
|
||||||
from gi.repository import Gtk
|
|
||||||
|
|
||||||
# pylint: disable=import-error
|
# pylint: disable=import-error
|
||||||
import gtkgui_helpers
|
import gtkgui_helpers
|
||||||
from common import gajim
|
from common import gajim
|
||||||
|
from dialogs import YesNoDialog
|
||||||
from plugins.gui import GajimPluginConfigDialog
|
from plugins.gui import GajimPluginConfigDialog
|
||||||
# pylint: enable=import-error
|
from axolotl.state.sessionrecord import SessionRecord
|
||||||
|
from common import configpaths
|
||||||
|
|
||||||
log = logging.getLogger('gajim.plugin_system.omemo')
|
log = logging.getLogger('gajim.plugin_system.omemo')
|
||||||
|
|
||||||
|
PILLOW = False
|
||||||
|
try:
|
||||||
|
import qrcode
|
||||||
|
PILLOW = True
|
||||||
|
except Exception as e:
|
||||||
|
log.exception('Error:')
|
||||||
|
log.error('python-qrcode or dependencies of it, are not available')
|
||||||
|
|
||||||
|
# pylint: enable=import-error
|
||||||
UNDECIDED = 2
|
UNDECIDED = 2
|
||||||
TRUSTED = 1
|
TRUSTED = 1
|
||||||
UNTRUSTED = 0
|
UNTRUSTED = 0
|
||||||
@@ -106,6 +118,12 @@ class Ui(object):
|
|||||||
self.account = self.contact.account.name
|
self.account = self.contact.account.name
|
||||||
self.windowinstances = {}
|
self.windowinstances = {}
|
||||||
|
|
||||||
|
self.groupchat = False
|
||||||
|
if chat_control.type_id == message_control.TYPE_GC:
|
||||||
|
self.groupchat = True
|
||||||
|
self.omemo_capable = False
|
||||||
|
self.room = self.chat_control.room_jid
|
||||||
|
|
||||||
self.display_omemo_state()
|
self.display_omemo_state()
|
||||||
self.refresh_auth_lock_icon()
|
self.refresh_auth_lock_icon()
|
||||||
|
|
||||||
@@ -133,6 +151,9 @@ class Ui(object):
|
|||||||
item.set_image(Gtk.Image.new_from_file(icon_path))
|
item.set_image(Gtk.Image.new_from_file(icon_path))
|
||||||
item.set_submenu(submenu)
|
item.set_submenu(submenu)
|
||||||
|
|
||||||
|
if self.groupchat:
|
||||||
|
item.set_sensitive(self.omemo_capable)
|
||||||
|
|
||||||
# at index 8 is the separator after the esession encryption entry
|
# at index 8 is the separator after the esession encryption entry
|
||||||
menu.insert(item, 8)
|
menu.insert(item, 8)
|
||||||
return menu
|
return menu
|
||||||
@@ -159,6 +180,37 @@ class Ui(object):
|
|||||||
log.debug(self.account + ' => Sending Message to ' +
|
log.debug(self.account + ' => Sending Message to ' +
|
||||||
self.contact.jid)
|
self.contact.jid)
|
||||||
|
|
||||||
|
def omemo_send_gc_message(message, xhtml=None, process_commands=True):
|
||||||
|
self.new_fingerprints_available()
|
||||||
|
if self.encryption_active():
|
||||||
|
missing = True
|
||||||
|
own_jid = gajim.get_jid_from_account(self.account)
|
||||||
|
for nick in self.plugin.groupchat[self.room]:
|
||||||
|
real_jid = self.plugin.groupchat[self.room][nick]
|
||||||
|
if real_jid == own_jid:
|
||||||
|
continue
|
||||||
|
if not self.plugin.are_keys_missing(self.account,
|
||||||
|
real_jid):
|
||||||
|
missing = False
|
||||||
|
if missing:
|
||||||
|
log.debug(self.account +
|
||||||
|
' => No Trusted Fingerprints for ' +
|
||||||
|
self.room)
|
||||||
|
self.no_trusted_fingerprints_warning()
|
||||||
|
else:
|
||||||
|
self.chat_control.orig_send_message(message, xhtml,
|
||||||
|
process_commands)
|
||||||
|
log.debug(self.account + ' => Sending Message to ' +
|
||||||
|
self.room)
|
||||||
|
else:
|
||||||
|
self.chat_control.orig_send_message(message, xhtml,
|
||||||
|
process_commands)
|
||||||
|
log.debug(self.account + ' => Sending Message to ' +
|
||||||
|
self.room)
|
||||||
|
|
||||||
|
if self.groupchat:
|
||||||
|
self.chat_control.send_message = omemo_send_gc_message
|
||||||
|
else:
|
||||||
self.chat_control.send_message = omemo_send_message
|
self.chat_control.send_message = omemo_send_message
|
||||||
|
|
||||||
def set_omemo_state(self, enabled):
|
def set_omemo_state(self, enabled):
|
||||||
@@ -182,6 +234,12 @@ class Ui(object):
|
|||||||
self.omemobutton.set_omemo_state(enabled)
|
self.omemobutton.set_omemo_state(enabled)
|
||||||
self.display_omemo_state()
|
self.display_omemo_state()
|
||||||
|
|
||||||
|
def sensitive(self, value):
|
||||||
|
self.omemobutton.set_sensitive(value)
|
||||||
|
self.omemo_capable = value
|
||||||
|
if value:
|
||||||
|
self.chat_control.prepare_context_menu
|
||||||
|
|
||||||
def encryption_active(self):
|
def encryption_active(self):
|
||||||
return self.state.encryption.is_active(self.contact.jid)
|
return self.state.encryption.is_active(self.contact.jid)
|
||||||
|
|
||||||
@@ -190,12 +248,27 @@ class Ui(object):
|
|||||||
self.set_omemo_state(True)
|
self.set_omemo_state(True)
|
||||||
|
|
||||||
def new_fingerprints_available(self):
|
def new_fingerprints_available(self):
|
||||||
fingerprints = self.state.store.getNewFingerprints(self.contact.jid)
|
jid = self.contact.jid
|
||||||
|
if self.groupchat and self.room in self.plugin.groupchat:
|
||||||
|
for nick in self.plugin.groupchat[self.room]:
|
||||||
|
real_jid = self.plugin.groupchat[self.room][nick]
|
||||||
|
fingerprints = self.state.store. \
|
||||||
|
getNewFingerprints(real_jid)
|
||||||
|
if fingerprints:
|
||||||
|
self.show_fingerprint_window(fingerprints)
|
||||||
|
elif not self.groupchat:
|
||||||
|
fingerprints = self.state.store.getNewFingerprints(jid)
|
||||||
if fingerprints:
|
if fingerprints:
|
||||||
self.show_fingerprint_window(fingerprints)
|
self.show_fingerprint_window(fingerprints)
|
||||||
|
|
||||||
def show_fingerprint_window(self, fingerprints=None):
|
def show_fingerprint_window(self, fingerprints=None):
|
||||||
if 'dialog' not in self.windowinstances:
|
if 'dialog' not in self.windowinstances:
|
||||||
|
if self.groupchat:
|
||||||
|
self.windowinstances['dialog'] = \
|
||||||
|
FingerprintWindow(self.plugin, self.contact,
|
||||||
|
self.chat_control.parent_win.window,
|
||||||
|
self.windowinstances, groupchat=True)
|
||||||
|
else:
|
||||||
self.windowinstances['dialog'] = \
|
self.windowinstances['dialog'] = \
|
||||||
FingerprintWindow(self.plugin, self.contact,
|
FingerprintWindow(self.plugin, self.contact,
|
||||||
self.chat_control.parent_win.window,
|
self.chat_control.parent_win.window,
|
||||||
@@ -229,6 +302,8 @@ class Ui(object):
|
|||||||
self.chat_control.print_conversation_line(msg, 'status', '', None)
|
self.chat_control.print_conversation_line(msg, 'status', '', None)
|
||||||
|
|
||||||
def refresh_auth_lock_icon(self):
|
def refresh_auth_lock_icon(self):
|
||||||
|
if self.groupchat:
|
||||||
|
return
|
||||||
if self.encryption_active():
|
if self.encryption_active():
|
||||||
if self.state.getUndecidedFingerprints(self.contact.jid):
|
if self.state.getUndecidedFingerprints(self.contact.jid):
|
||||||
self.chat_control._show_lock_image(True, 'OMEMO', True, True,
|
self.chat_control._show_lock_image(True, 'OMEMO', True, True,
|
||||||
@@ -242,6 +317,8 @@ class Ui(object):
|
|||||||
|
|
||||||
def removeUi(self):
|
def removeUi(self):
|
||||||
self.actions_hbox.remove(self.omemobutton)
|
self.actions_hbox.remove(self.omemobutton)
|
||||||
|
self.chat_control._show_lock_image(False, 'OMEMO', False, True,
|
||||||
|
False)
|
||||||
self.chat_control.prepare_context_menu = \
|
self.chat_control.prepare_context_menu = \
|
||||||
self.chat_control.omemo_orig_prepare_context_menu
|
self.chat_control.omemo_orig_prepare_context_menu
|
||||||
self.chat_control.send_message = self.chat_control.orig_send_message
|
self.chat_control.send_message = self.chat_control.orig_send_message
|
||||||
@@ -256,40 +333,144 @@ class OMEMOConfigDialog(GajimPluginConfigDialog):
|
|||||||
self.B.set_translation_domain('gajim_plugins')
|
self.B.set_translation_domain('gajim_plugins')
|
||||||
self.B.add_from_file(self.GTK_BUILDER_FILE_PATH)
|
self.B.add_from_file(self.GTK_BUILDER_FILE_PATH)
|
||||||
|
|
||||||
self.fpr_model = Gtk.ListStore(GObject.TYPE_INT,
|
try:
|
||||||
GObject.TYPE_STRING,
|
self.disabled_accounts = self.plugin.config['DISABLED_ACCOUNTS']
|
||||||
GObject.TYPE_STRING,
|
except KeyError:
|
||||||
GObject.TYPE_STRING)
|
self.plugin.config['DISABLED_ACCOUNTS'] = []
|
||||||
|
self.disabled_accounts = self.plugin.config['DISABLED_ACCOUNTS']
|
||||||
|
|
||||||
self.device_model = Gtk.ListStore(GObject.TYPE_INT)
|
log.debug('Disabled Accounts:')
|
||||||
|
log.debug(self.disabled_accounts)
|
||||||
|
|
||||||
self.account_store = self.B.get_object('account_store')
|
self.fpr_model = self.B.get_object('fingerprint_store')
|
||||||
|
self.device_model = self.B.get_object('deviceid_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 = 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.disabled_acc_store = self.B.get_object('disabled_account_store')
|
||||||
self.device_view.set_model(self.device_model)
|
self.account_store = self.B.get_object('account_store')
|
||||||
|
|
||||||
if len(self.account_store) > 0:
|
self.active_acc_view = self.B.get_object('active_accounts_view')
|
||||||
self.B.get_object('account_combobox').set_active(0)
|
self.disabled_acc_view = self.B.get_object('disabled_accounts_view')
|
||||||
|
|
||||||
vbox = self.get_content_area()
|
vbox = self.get_content_area()
|
||||||
vbox.pack_start(self.B.get_object('notebook1'), True, True, 0)
|
vbox.pack_start(self.B.get_object('notebook1'), True, True, 0)
|
||||||
|
|
||||||
self.B.connect_signals(self)
|
self.B.connect_signals(self)
|
||||||
|
|
||||||
|
self.plugin_active = False
|
||||||
|
|
||||||
def on_run(self):
|
def on_run(self):
|
||||||
self.update_context_list()
|
for plugin in gajim.plugin_manager.active_plugins:
|
||||||
self.account_combobox_changed_cb(self.B.get_object('account_combobox'))
|
log.debug(type(plugin))
|
||||||
|
if type(plugin).__name__ == 'OmemoPlugin':
|
||||||
|
self.plugin_active = True
|
||||||
|
break
|
||||||
|
self.update_account_store()
|
||||||
|
self.update_account_combobox()
|
||||||
|
self.update_disabled_account_view()
|
||||||
|
|
||||||
|
def is_in_accountstore(self, account):
|
||||||
|
for row in self.account_store:
|
||||||
|
if row[0] == account:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def update_account_store(self):
|
||||||
|
for account in sorted(gajim.contacts.get_accounts()):
|
||||||
|
if account not in self.disabled_accounts and \
|
||||||
|
not self.is_in_accountstore(account):
|
||||||
|
self.account_store.append(row=(account,))
|
||||||
|
|
||||||
|
def update_account_combobox(self):
|
||||||
|
if self.plugin_active is False:
|
||||||
|
return
|
||||||
|
if len(self.account_store) > 0:
|
||||||
|
self.B.get_object('account_combobox').set_active(0)
|
||||||
|
else:
|
||||||
|
self.account_combobox_changed_cb(
|
||||||
|
self.B.get_object('account_combobox'))
|
||||||
|
|
||||||
def account_combobox_changed_cb(self, box, *args):
|
def account_combobox_changed_cb(self, box, *args):
|
||||||
self.update_context_list()
|
self.update_context_list()
|
||||||
|
|
||||||
|
def get_qrcode(self, jid, sid, fingerprint):
|
||||||
|
file_name = 'omemo_{}.png'.format(jid)
|
||||||
|
path = os.path.join(
|
||||||
|
configpaths.gajimpaths['MY_DATA'], file_name)
|
||||||
|
|
||||||
|
ver_string = 'xmpp:{}?omemo-sid-{}={}'.format(jid, sid, fingerprint)
|
||||||
|
log.debug('Verification String: ' + ver_string)
|
||||||
|
|
||||||
|
if os.path.exists(path):
|
||||||
|
return path
|
||||||
|
|
||||||
|
qr = qrcode.QRCode(version=None, error_correction=2, box_size=4, border=1)
|
||||||
|
qr.add_data(ver_string)
|
||||||
|
qr.make(fit=True)
|
||||||
|
img = qr.make_image()
|
||||||
|
img.save(path)
|
||||||
|
return path
|
||||||
|
|
||||||
|
def update_disabled_account_view(self):
|
||||||
|
self.disabled_acc_store.clear()
|
||||||
|
for account in self.disabled_accounts:
|
||||||
|
self.disabled_acc_store.append(row=(account,))
|
||||||
|
|
||||||
|
def activate_accounts_btn_clicked(self, button, *args):
|
||||||
|
mod, paths = self.disabled_acc_view.get_selection().get_selected_rows()
|
||||||
|
for path in paths:
|
||||||
|
it = mod.get_iter(path)
|
||||||
|
account = mod.get(it, 0)
|
||||||
|
if account[0] in self.disabled_accounts and \
|
||||||
|
not self.is_in_accountstore(account[0]):
|
||||||
|
self.account_store.append(row=(account[0],))
|
||||||
|
self.disabled_accounts.remove(account[0])
|
||||||
|
self.update_disabled_account_view()
|
||||||
|
self.plugin.config['DISABLED_ACCOUNTS'] = self.disabled_accounts
|
||||||
|
self.update_account_combobox()
|
||||||
|
|
||||||
|
def disable_accounts_btn_clicked(self, button, *args):
|
||||||
|
mod, paths = self.active_acc_view.get_selection().get_selected_rows()
|
||||||
|
for path in paths:
|
||||||
|
it = mod.get_iter(path)
|
||||||
|
account = mod.get(it, 0)
|
||||||
|
if account[0] not in self.disabled_accounts and \
|
||||||
|
self.is_in_accountstore(account[0]):
|
||||||
|
self.disabled_accounts.append(account[0])
|
||||||
|
self.account_store.remove(it)
|
||||||
|
self.update_disabled_account_view()
|
||||||
|
self.plugin.config['DISABLED_ACCOUNTS'] = self.disabled_accounts
|
||||||
|
self.update_account_combobox()
|
||||||
|
|
||||||
|
def delfpr_button_clicked(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()
|
||||||
|
|
||||||
|
def on_yes(checked):
|
||||||
|
record = state.store.loadSession(jid, deviceid)
|
||||||
|
identity_key = record.getSessionState().getRemoteIdentityKey()
|
||||||
|
|
||||||
|
state.store.deleteSession(jid, deviceid)
|
||||||
|
state.store.deleteIdentity(jid, identity_key)
|
||||||
|
self.update_context_list()
|
||||||
|
|
||||||
|
for path in paths:
|
||||||
|
it = mod.get_iter(path)
|
||||||
|
jid, fpr, deviceid = mod.get(it, 1, 3, 4)
|
||||||
|
fpr = fpr[31:-12]
|
||||||
|
|
||||||
|
YesNoDialog(
|
||||||
|
'Delete Fingerprint?',
|
||||||
|
'Do you want to delete the '
|
||||||
|
'fingerprint of <b>{}</b> on your account <b>{}</b>?'
|
||||||
|
'\n\n<tt>{}</tt>'.format(jid, account, fpr),
|
||||||
|
on_response_yes=on_yes, transient_for=self)
|
||||||
|
|
||||||
def trust_button_clicked_cb(self, button, *args):
|
def trust_button_clicked_cb(self, button, *args):
|
||||||
active = self.B.get_object('account_combobox').get_active()
|
active = self.B.get_object('account_combobox').get_active()
|
||||||
account = self.account_store[active][0]
|
account = self.account_store[active][0]
|
||||||
@@ -298,44 +479,43 @@ class OMEMOConfigDialog(GajimPluginConfigDialog):
|
|||||||
|
|
||||||
mod, paths = self.fpr_view.get_selection().get_selected_rows()
|
mod, paths = self.fpr_view.get_selection().get_selected_rows()
|
||||||
|
|
||||||
for path in paths:
|
def on_yes(checked, identity_key):
|
||||||
it = mod.get_iter(path)
|
state.store.setTrust(identity_key, TRUSTED)
|
||||||
_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:
|
try:
|
||||||
if self.plugin.ui_list[account]:
|
if self.plugin.ui_list[account]:
|
||||||
self.plugin.ui_list[account][user].refresh_auth_lock_icon()
|
self.plugin.ui_list[account][jid]. \
|
||||||
|
refresh_auth_lock_icon()
|
||||||
except:
|
except:
|
||||||
dlg.destroy()
|
log.debug('UI not available')
|
||||||
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()
|
self.update_context_list()
|
||||||
|
|
||||||
|
def on_no(identity_key):
|
||||||
|
state.store.setTrust(identity_key, UNTRUSTED)
|
||||||
|
try:
|
||||||
|
if jid in self.plugin.ui_list[account]:
|
||||||
|
self.plugin.ui_list[account][jid]. \
|
||||||
|
refresh_auth_lock_icon()
|
||||||
|
except:
|
||||||
|
log.debug('UI not available')
|
||||||
|
self.update_context_list()
|
||||||
|
|
||||||
|
for path in paths:
|
||||||
|
it = mod.get_iter(path)
|
||||||
|
jid, fpr, deviceid = mod.get(it, 1, 3, 4)
|
||||||
|
fpr = fpr[31:-12]
|
||||||
|
|
||||||
|
record = state.store.loadSession(jid, deviceid)
|
||||||
|
identity_key = record.getSessionState().getRemoteIdentityKey()
|
||||||
|
|
||||||
|
YesNoDialog(
|
||||||
|
'Trust / Revoke Fingerprint?',
|
||||||
|
'Do you want to trust the fingerprint of <b>{}</b> '
|
||||||
|
'on your account <b>{}</b>?\n\n'
|
||||||
|
'<tt>{}</tt>'.format(jid, account, fpr),
|
||||||
|
on_response_yes=(on_yes, identity_key),
|
||||||
|
on_response_no=(on_no, identity_key),
|
||||||
|
transient_for=self)
|
||||||
|
|
||||||
def cleardevice_button_clicked_cb(self, button, *args):
|
def cleardevice_button_clicked_cb(self, button, *args):
|
||||||
active = self.B.get_object('account_combobox').get_active()
|
active = self.B.get_object('account_combobox').get_active()
|
||||||
account = self.account_store[active][0]
|
account = self.account_store[active][0]
|
||||||
@@ -380,67 +560,94 @@ class OMEMOConfigDialog(GajimPluginConfigDialog):
|
|||||||
def update_context_list(self):
|
def update_context_list(self):
|
||||||
self.fpr_model.clear()
|
self.fpr_model.clear()
|
||||||
self.device_model.clear()
|
self.device_model.clear()
|
||||||
|
self.qrcode = self.B.get_object('qrcode')
|
||||||
|
self.qrinfo = self.B.get_object('qrinfo')
|
||||||
|
if len(self.account_store) == 0:
|
||||||
|
self.B.get_object('ID').set_markup('')
|
||||||
|
self.B.get_object('fingerprint_label').set_markup('')
|
||||||
|
self.B.get_object('trust_button').set_sensitive(False)
|
||||||
|
self.B.get_object('delfprbutton').set_sensitive(False)
|
||||||
|
self.B.get_object('refresh').set_sensitive(False)
|
||||||
|
self.B.get_object('cleardevice_button').set_sensitive(False)
|
||||||
|
self.B.get_object('qrcode').clear()
|
||||||
|
return
|
||||||
active = self.B.get_object('account_combobox').get_active()
|
active = self.B.get_object('account_combobox').get_active()
|
||||||
account = self.account_store[active][0]
|
account = self.account_store[active][0]
|
||||||
state = self.plugin.get_omemo_state(account)
|
|
||||||
|
|
||||||
|
# Set buttons active
|
||||||
|
self.B.get_object('trust_button').set_sensitive(True)
|
||||||
|
self.B.get_object('delfprbutton').set_sensitive(True)
|
||||||
|
self.B.get_object('refresh').set_sensitive(True)
|
||||||
|
if account == 'Local':
|
||||||
|
self.B.get_object('cleardevice_button').set_sensitive(False)
|
||||||
|
else:
|
||||||
|
self.B.get_object('cleardevice_button').set_sensitive(True)
|
||||||
|
|
||||||
|
# Set FPR Label and DeviceID
|
||||||
|
state = self.plugin.get_omemo_state(account)
|
||||||
deviceid = state.own_device_id
|
deviceid = state.own_device_id
|
||||||
self.B.get_object('ID').set_markup('<tt>%s</tt>' % deviceid)
|
self.B.get_object('ID').set_markup('<tt>%s</tt>' % deviceid)
|
||||||
|
|
||||||
ownfpr = binascii.hexlify(state.store.getIdentityKeyPair()
|
ownfpr = binascii.hexlify(state.store.getIdentityKeyPair()
|
||||||
.getPublicKey().serialize()).decode('utf-8')
|
.getPublicKey().serialize()).decode('utf-8')
|
||||||
ownfpr = self.human_hash(ownfpr[2:])
|
human_ownfpr = human_hash(ownfpr[2:])
|
||||||
self.B.get_object('fingerprint_label').set_markup('<tt>%s</tt>'
|
self.B.get_object('fingerprint_label').set_markup('<tt>%s</tt>'
|
||||||
% ownfpr)
|
% human_ownfpr)
|
||||||
|
|
||||||
fprDB = state.store.identityKeyStore.getAllFingerprints()
|
# Set Fingerprint List
|
||||||
activeSessions = state.store.sessionStore. \
|
trust_str = {0: 'False', 1: 'True', 2: 'Undecided'}
|
||||||
getAllActiveSessionsKeys()
|
session_db = state.store.getAllSessions()
|
||||||
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 session_db:
|
||||||
|
color = {0: '#FF0040', # red
|
||||||
|
1: '#2EFE2E', # green
|
||||||
|
2: '#FF8000'} # orange
|
||||||
|
|
||||||
|
_id, jid, deviceid, record, active = item
|
||||||
|
|
||||||
|
active = bool(active)
|
||||||
|
|
||||||
|
identity_key = SessionRecord(serialized=record). \
|
||||||
|
getSessionState().getRemoteIdentityKey()
|
||||||
|
fpr = binascii.hexlify(
|
||||||
|
identity_key.getPublicKey().serialize()).decode('utf-8')
|
||||||
|
fpr = human_hash(fpr[2:])
|
||||||
|
|
||||||
|
trust = state.store.isTrustedIdentity(jid, identity_key)
|
||||||
|
|
||||||
|
if not active:
|
||||||
|
color[trust] = '#585858' # grey
|
||||||
|
|
||||||
|
self.fpr_model.append(
|
||||||
|
(_id, jid, trust_str[trust],
|
||||||
|
'<tt><span foreground="{}">{}</span></tt>'.
|
||||||
|
format(color[trust], fpr),
|
||||||
|
deviceid))
|
||||||
|
|
||||||
|
# Set Device ID List
|
||||||
for item in state.own_devices:
|
for item in state.own_devices:
|
||||||
self.device_model.append([item])
|
self.device_model.append([item])
|
||||||
|
|
||||||
def human_hash(self, fpr):
|
# Set QR Verification Code
|
||||||
fpr = fpr.upper()
|
if PILLOW:
|
||||||
fplen = len(fpr)
|
path = self.get_qrcode(
|
||||||
wordsize = fplen // 8
|
gajim.get_jid_from_account(account), deviceid, ownfpr[2:])
|
||||||
buf = ''
|
self.qrcode.set_from_pixbuf(GdkPixbuf.Pixbuf.new_from_file(path))
|
||||||
for w in range(0, fplen, wordsize):
|
self.qrinfo.hide()
|
||||||
buf += '{0} '.format(fpr[w:w + wordsize])
|
else:
|
||||||
return buf.rstrip()
|
self.qrinfo.show()
|
||||||
|
|
||||||
|
|
||||||
class FingerprintWindow(Gtk.Dialog):
|
class FingerprintWindow(Gtk.Dialog):
|
||||||
def __init__(self, plugin, contact, parent, windowinstances):
|
def __init__(self, plugin, contact, parent, windowinstances,
|
||||||
|
groupchat=False):
|
||||||
|
self.groupchat = groupchat
|
||||||
self.contact = contact
|
self.contact = contact
|
||||||
self.windowinstances = windowinstances
|
self.windowinstances = windowinstances
|
||||||
|
self.account = self.contact.account.name
|
||||||
|
self.plugin = plugin
|
||||||
|
self.omemostate = self.plugin.get_omemo_state(self.account)
|
||||||
|
self.own_jid = gajim.get_jid_from_account(self.account)
|
||||||
Gtk.Dialog.__init__(self,
|
Gtk.Dialog.__init__(self,
|
||||||
title=('Fingerprints for %s') % contact.jid,
|
title=('Fingerprints for %s') % contact.jid,
|
||||||
parent=parent,
|
parent=parent,
|
||||||
@@ -448,45 +655,34 @@ class FingerprintWindow(Gtk.Dialog):
|
|||||||
close_button = self.add_button(Gtk.STOCK_CLOSE, Gtk.ResponseType.CLOSE)
|
close_button = self.add_button(Gtk.STOCK_CLOSE, Gtk.ResponseType.CLOSE)
|
||||||
close_button.connect('clicked', self.on_close_button_clicked)
|
close_button.connect('clicked', self.on_close_button_clicked)
|
||||||
self.connect('delete-event', self.on_window_delete)
|
self.connect('delete-event', self.on_window_delete)
|
||||||
self.plugin = plugin
|
|
||||||
self.GTK_BUILDER_FILE_PATH = \
|
self.GTK_BUILDER_FILE_PATH = \
|
||||||
self.plugin.local_file_path('fpr_dialog.ui')
|
self.plugin.local_file_path('fpr_dialog.ui')
|
||||||
self.B = Gtk.Builder()
|
self.xml = Gtk.Builder()
|
||||||
self.B.set_translation_domain('gajim_plugins')
|
self.xml.add_from_file(self.GTK_BUILDER_FILE_PATH)
|
||||||
self.B.add_from_file(self.GTK_BUILDER_FILE_PATH)
|
self.xml.set_translation_domain('gajim_plugins')
|
||||||
|
|
||||||
self.fpr_model = Gtk.ListStore(GObject.TYPE_INT,
|
self.fpr_model = self.xml.get_object('fingerprint_store')
|
||||||
GObject.TYPE_STRING,
|
|
||||||
GObject.TYPE_STRING,
|
|
||||||
GObject.TYPE_STRING)
|
|
||||||
|
|
||||||
self.fpr_view = self.B.get_object('fingerprint_view')
|
self.fpr_view = self.xml.get_object('fingerprint_view')
|
||||||
self.fpr_view.set_model(self.fpr_model)
|
self.fpr_view_own = self.xml.get_object('fingerprint_view_own')
|
||||||
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')
|
|
||||||
|
|
||||||
|
self.notebook = self.xml.get_object('notebook1')
|
||||||
vbox = self.get_content_area()
|
vbox = self.get_content_area()
|
||||||
vbox.pack_start(self.notebook, True, True, 0)
|
vbox.pack_start(self.notebook, True, True, 0)
|
||||||
|
|
||||||
self.B.connect_signals(self)
|
self.xml.connect_signals(self)
|
||||||
|
|
||||||
self.account = self.contact.account.name
|
|
||||||
self.omemostate = self.plugin.get_omemo_state(self.account)
|
|
||||||
|
|
||||||
|
# Set own Fingerprint Label
|
||||||
ownfpr = binascii.hexlify(self.omemostate.store.getIdentityKeyPair()
|
ownfpr = binascii.hexlify(self.omemostate.store.getIdentityKeyPair()
|
||||||
.getPublicKey().serialize()).decode('utf-8')
|
.getPublicKey().serialize()).decode('utf-8')
|
||||||
ownfpr = self.human_hash(ownfpr[2:])
|
ownfpr = human_hash(ownfpr[2:])
|
||||||
|
self.xml.get_object('fingerprint_label_own').set_markup('<tt>%s</tt>'
|
||||||
self.B.get_object('fingerprint_label_own').set_markup('<tt>%s</tt>'
|
|
||||||
% ownfpr)
|
% ownfpr)
|
||||||
|
|
||||||
self.update_context_list()
|
self.update_context_list()
|
||||||
|
|
||||||
|
self.show_all()
|
||||||
|
|
||||||
def on_close_button_clicked(self, widget):
|
def on_close_button_clicked(self, widget):
|
||||||
del self.windowinstances['dialog']
|
del self.windowinstances['dialog']
|
||||||
self.hide()
|
self.hide()
|
||||||
@@ -496,43 +692,43 @@ class FingerprintWindow(Gtk.Dialog):
|
|||||||
self.hide()
|
self.hide()
|
||||||
|
|
||||||
def trust_button_clicked_cb(self, button, *args):
|
def trust_button_clicked_cb(self, button, *args):
|
||||||
|
state = self.omemostate
|
||||||
|
|
||||||
if self.notebook.get_current_page() == 1:
|
if self.notebook.get_current_page() == 1:
|
||||||
mod, paths = self.fpr_view_own.get_selection().get_selected_rows()
|
mod, paths = self.fpr_view_own.get_selection().get_selected_rows()
|
||||||
else:
|
else:
|
||||||
mod, paths = self.fpr_view.get_selection().get_selected_rows()
|
mod, paths = self.fpr_view.get_selection().get_selected_rows()
|
||||||
|
|
||||||
|
def on_yes(checked, identity_key):
|
||||||
|
state.store.setTrust(identity_key, TRUSTED)
|
||||||
|
if not self.groupchat:
|
||||||
|
self.plugin.ui_list[self.account][self.contact.jid]. \
|
||||||
|
refresh_auth_lock_icon()
|
||||||
|
self.update_context_list()
|
||||||
|
|
||||||
|
def on_no(identity_key):
|
||||||
|
state.store.setTrust(identity_key, UNTRUSTED)
|
||||||
|
if not self.groupchat:
|
||||||
|
self.plugin.ui_list[self.account][self.contact.jid]. \
|
||||||
|
refresh_auth_lock_icon()
|
||||||
|
self.update_context_list()
|
||||||
|
|
||||||
for path in paths:
|
for path in paths:
|
||||||
it = mod.get_iter(path)
|
it = mod.get_iter(path)
|
||||||
_id, user, fpr = mod.get(it, 0, 1, 3)
|
jid, fpr, deviceid = mod.get(it, 1, 3, 4)
|
||||||
fpr = fpr[31:-12]
|
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()
|
record = state.store.loadSession(jid, deviceid)
|
||||||
|
identity_key = record.getSessionState().getRemoteIdentityKey()
|
||||||
|
|
||||||
|
YesNoDialog(
|
||||||
|
'Trust / Revoke Fingerprint?',
|
||||||
|
'Do you want to trust the fingerprint of <b>{}</b> '
|
||||||
|
'on your account <b>{}</b>?\n\n'
|
||||||
|
'<tt>{}</tt>'.format(jid, self.account, fpr),
|
||||||
|
on_response_yes=(on_yes, identity_key),
|
||||||
|
on_response_no=(on_no, identity_key),
|
||||||
|
transient_for=self)
|
||||||
|
|
||||||
def fpr_button_pressed_cb(self, tw, event):
|
def fpr_button_pressed_cb(self, tw, event):
|
||||||
if event.button == 3:
|
if event.button == 3:
|
||||||
@@ -547,7 +743,7 @@ class FingerprintWindow(Gtk.Dialog):
|
|||||||
# selection, otherwise we only select the new item
|
# selection, otherwise we only select the new item
|
||||||
keep_selection = tw.get_selection().path_is_selected(pthinfo[0])
|
keep_selection = tw.get_selection().path_is_selected(pthinfo[0])
|
||||||
|
|
||||||
pop = self.B.get_object('fprclipboard_menu')
|
pop = self.xml.get_object('fprclipboard_menu')
|
||||||
pop.popup(None, None, None, event.button, event.time)
|
pop.popup(None, None, None, event.button, event.time)
|
||||||
|
|
||||||
# keep_selection=True -> no further processing of click event
|
# keep_selection=True -> no further processing of click event
|
||||||
@@ -571,45 +767,52 @@ class FingerprintWindow(Gtk.Dialog):
|
|||||||
|
|
||||||
def update_context_list(self, *args):
|
def update_context_list(self, *args):
|
||||||
self.fpr_model.clear()
|
self.fpr_model.clear()
|
||||||
|
state = self.omemostate
|
||||||
|
|
||||||
if self.notebook.get_current_page() == 1:
|
if self.notebook.get_current_page() == 1:
|
||||||
jid = gajim.get_jid_from_account(self.account)
|
contact_jid = self.own_jid
|
||||||
else:
|
else:
|
||||||
jid = self.contact.jid
|
contact_jid = self.contact.jid
|
||||||
|
|
||||||
fprDB = self.omemostate.store.identityKeyStore.getFingerprints(jid)
|
trust_str = {0: 'False', 1: 'True', 2: 'Undecided'}
|
||||||
activeSessions = self.omemostate.store.sessionStore. \
|
if self.groupchat and self.notebook.get_current_page() == 0:
|
||||||
getActiveSessionsKeys(jid)
|
contact_jids = []
|
||||||
|
for nick in self.plugin.groupchat[contact_jid]:
|
||||||
|
real_jid = self.plugin.groupchat[contact_jid][nick]
|
||||||
|
if real_jid == self.own_jid:
|
||||||
|
continue
|
||||||
|
contact_jids.append(real_jid)
|
||||||
|
session_db = state.store.getSessionsFromJids(contact_jids)
|
||||||
|
else:
|
||||||
|
session_db = state.store.getSessionsFromJid(contact_jid)
|
||||||
|
|
||||||
for item in fprDB:
|
for item in session_db:
|
||||||
_id, jid, fpr, tr = item
|
color = {0: '#FF0040', # red
|
||||||
active = fpr in activeSessions
|
1: '#2EFE2E', # green
|
||||||
fpr = binascii.hexlify(fpr).decode('utf-8')
|
2: '#FF8000'} # orange
|
||||||
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):
|
_id, jid, deviceid, record, active = item
|
||||||
|
|
||||||
|
active = bool(active)
|
||||||
|
|
||||||
|
identity_key = SessionRecord(serialized=record). \
|
||||||
|
getSessionState().getRemoteIdentityKey()
|
||||||
|
fpr = binascii.hexlify(identity_key.getPublicKey().serialize()).decode('utf-8')
|
||||||
|
fpr = human_hash(fpr[2:])
|
||||||
|
|
||||||
|
trust = state.store.isTrustedIdentity(jid, identity_key)
|
||||||
|
|
||||||
|
if not active:
|
||||||
|
color[trust] = '#585858' # grey
|
||||||
|
|
||||||
|
self.fpr_model.append(
|
||||||
|
(_id, jid, trust_str[trust],
|
||||||
|
'<tt><span foreground="{}">{}</span></tt>'.
|
||||||
|
format(color[trust], fpr),
|
||||||
|
deviceid))
|
||||||
|
|
||||||
|
|
||||||
|
def human_hash(fpr):
|
||||||
fpr = fpr.upper()
|
fpr = fpr.upper()
|
||||||
fplen = len(fpr)
|
fplen = len(fpr)
|
||||||
wordsize = fplen // 8
|
wordsize = fplen // 8
|
||||||
|
|||||||
@@ -79,10 +79,14 @@ class OmemoMessage(Node):
|
|||||||
# , contact_jid, key, iv, payload, dev_id, my_dev_id):
|
# , contact_jid, key, iv, payload, dev_id, my_dev_id):
|
||||||
Node.__init__(self, 'encrypted', attrs={'xmlns': NS_OMEMO})
|
Node.__init__(self, 'encrypted', attrs={'xmlns': NS_OMEMO})
|
||||||
header = Node('header', attrs={'sid': msg_dict['sid']})
|
header = Node('header', attrs={'sid': msg_dict['sid']})
|
||||||
for rid, key in msg_dict['keys'].items():
|
for rid, (key, prekey) in msg_dict['keys'].items():
|
||||||
header.addChild('key', attrs={'rid': rid}).addData(b64encode(key)
|
if prekey:
|
||||||
.decode('utf-8'))
|
child = header.addChild('key',
|
||||||
|
attrs={'prekey': 'true', 'rid': rid})
|
||||||
|
else:
|
||||||
|
child = header.addChild('key',
|
||||||
|
attrs={'rid': rid})
|
||||||
|
child.addData(b64encode(key).decode('utf-8'))
|
||||||
header.addChild('iv').addData(b64encode(msg_dict['iv']).decode('utf-8'))
|
header.addChild('iv').addData(b64encode(msg_dict['iv']).decode('utf-8'))
|
||||||
self.addChild(node=header)
|
self.addChild(node=header)
|
||||||
self.addChild('payload').addData(b64encode(msg_dict['payload'])
|
self.addChild('payload').addData(b64encode(msg_dict['payload'])
|
||||||
|
|||||||
Reference in New Issue
Block a user