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,8 +1,33 @@
|
||||
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
|
||||
- Send INFO message to resources who dont support OMEMO
|
||||
- Check dependencys and give correct error message
|
||||
- Dont process PreKeyWhisperMessages without PreKey
|
||||
- Dont process PGP messages
|
||||
- Send INFO message to resources who dont support OMEMO
|
||||
- Check dependencys and give correct error message
|
||||
- Dont process PreKeyWhisperMessages without PreKey
|
||||
- Dont process PGP messages
|
||||
|
||||
0.8.1 / 2016-08-05
|
||||
- Query own Device Bundles on send button press
|
||||
|
||||
@@ -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 -*-
|
||||
#
|
||||
# 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)
|
||||
from .omemoplugin import OmemoPlugin
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,298 +1,299 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<requires lib="gtk+" version="2.16"/>
|
||||
<!-- interface-naming-policy toplevel-contextual -->
|
||||
<object class="GtkListStore" id="account_store">
|
||||
<columns>
|
||||
<!-- column-name accountname -->
|
||||
<column type="gchararray"/>
|
||||
</columns>
|
||||
</object>
|
||||
<object class="GtkListStore" id="fingerprint_store">
|
||||
<columns>
|
||||
<!-- column-name id -->
|
||||
<column type="gint"/>
|
||||
<!-- column-name screenname -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name trust -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name fingerprint -->
|
||||
<column type="gchararray"/>
|
||||
</columns>
|
||||
</object>
|
||||
<object class="GtkNotebook" id="notebook1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<signal name="switch-page" handler="update_context_list" after="yes" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkVBox" id="vbox4">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="border_width">12</property>
|
||||
<property name="spacing">10</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="scrolledwindow1">
|
||||
<property name="height_request">200</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="hscrollbar_policy">automatic</property>
|
||||
<property name="vscrollbar_policy">automatic</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="fingerprint_view">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="model">fingerprint_store</property>
|
||||
<property name="search_column">0</property>
|
||||
<property name="tooltip_column">3</property>
|
||||
<signal name="button-press-event" handler="fpr_button_pressed_cb" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="name_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="title">Name</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="cellrenderertext2"/>
|
||||
<attributes>
|
||||
<attribute name="text">1</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="trust_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="title">Trust</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="cellrenderertoggle1"/>
|
||||
<attributes>
|
||||
<attribute name="text">2</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="fingerprint_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="title">Fingerprint</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="cellrenderertext4"/>
|
||||
<attributes>
|
||||
<attribute name="markup">3</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkHBox" id="hbox3">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="trust_button">
|
||||
<property name="label" translatable="yes" comments="button">Trust/Revoke Fingerprint</property>
|
||||
<property name="width_request">200</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<signal name="clicked" handler="trust_button_clicked_cb" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="tab">
|
||||
<object class="GtkLabel" id="label3">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes" comments="tab label">Contact</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="tab_fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkVBox" id="vbox1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="border_width">12</property>
|
||||
<property name="spacing">10</property>
|
||||
<child>
|
||||
<object class="GtkHBox" id="hbox1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">10</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="fingerprint_label_desc1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes" comments="Descriptive label">Own Fingerprint:</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="fingerprint_label_own">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label"><tt>-------- -------- -------- -------- -------- </tt></property>
|
||||
<property name="use_markup">True</property>
|
||||
<property name="selectable">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="scrolledwindow2">
|
||||
<property name="height_request">100</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="hscrollbar_policy">automatic</property>
|
||||
<property name="vscrollbar_policy">automatic</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="fingerprint_view_own">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="has_tooltip">True</property>
|
||||
<property name="model">fingerprint_store</property>
|
||||
<property name="headers_clickable">False</property>
|
||||
<property name="search_column">0</property>
|
||||
<property name="tooltip_column">3</property>
|
||||
<signal name="button-press-event" handler="fpr_button_pressed_cb" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="name_column1">
|
||||
<property name="resizable">True</property>
|
||||
<property name="title">Name</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="cellrenderertext1"/>
|
||||
<attributes>
|
||||
<attribute name="text">1</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="trust_column1">
|
||||
<property name="resizable">True</property>
|
||||
<property name="title">Trust</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="cellrenderertoggle2"/>
|
||||
<attributes>
|
||||
<attribute name="text">2</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="fingerprint_column1">
|
||||
<property name="resizable">True</property>
|
||||
<property name="title">Fingerprint</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="cellrenderertext3"/>
|
||||
<attributes>
|
||||
<attribute name="markup">3</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkHBox" id="hbox4">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="trust_button1">
|
||||
<property name="label" translatable="yes" comments="button">Trust/Revoke Fingerprint</property>
|
||||
<property name="width_request">200</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<signal name="clicked" handler="trust_button_clicked_cb" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child type="tab">
|
||||
<object class="GtkLabel" id="label1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Own Devices</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
<property name="tab_fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkMenu" id="fprclipboard_menu">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkMenuItem" id="copyfprclipboard_item">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes" comments="Context menu item">Copy to clipboard</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="clipboard_button_cb" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.18.3 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.0"/>
|
||||
<object class="GtkListStore" id="fingerprint_store">
|
||||
<columns>
|
||||
<!-- column-name id -->
|
||||
<column type="gint"/>
|
||||
<!-- column-name screenname -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name trust -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name fingerprint -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name deviceid -->
|
||||
<column type="gint"/>
|
||||
</columns>
|
||||
</object>
|
||||
<object class="GtkNotebook" id="notebook1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<signal name="switch-page" handler="update_context_list" after="yes" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkVBox" id="vbox4">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="border_width">12</property>
|
||||
<property name="spacing">10</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="scrolledwindow1">
|
||||
<property name="height_request">200</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="fingerprint_view">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="has_tooltip">True</property>
|
||||
<property name="model">fingerprint_store</property>
|
||||
<property name="headers_clickable">False</property>
|
||||
<property name="search_column">0</property>
|
||||
<property name="tooltip_column">3</property>
|
||||
<signal name="button-press-event" handler="fpr_button_pressed_cb" swapped="no"/>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection" id="treeview-selection1"/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="name_column">
|
||||
<property name="resizable">True</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>
|
||||
<object class="GtkCellRendererText" id="NameCell"/>
|
||||
<attributes>
|
||||
<attribute name="text">1</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="trust_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="title">Trust</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="TrustCell"/>
|
||||
<attributes>
|
||||
<attribute name="text">2</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="fingerprint_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="title">Fingerprint</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="FingerprintCell"/>
|
||||
<attributes>
|
||||
<attribute name="markup">3</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkHBox" id="hbox3">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="trust_button">
|
||||
<property name="label" translatable="yes" comments="button">Trust/Revoke Fingerprint</property>
|
||||
<property name="width_request">200</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<signal name="clicked" handler="trust_button_clicked_cb" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="tab">
|
||||
<object class="GtkLabel" id="label3">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes" comments="tab label">Contact</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="tab_fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkVBox" id="vbox1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="border_width">12</property>
|
||||
<property name="spacing">10</property>
|
||||
<child>
|
||||
<object class="GtkHBox" id="hbox1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">10</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="fingerprint_label_desc1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes" comments="Descriptive label">Own Fingerprint:</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="fingerprint_label_own">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label"><tt>-------- -------- -------- -------- -------- </tt></property>
|
||||
<property name="use_markup">True</property>
|
||||
<property name="selectable">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="scrolledwindow2">
|
||||
<property name="height_request">100</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="fingerprint_view_own">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="has_tooltip">True</property>
|
||||
<property name="model">fingerprint_store</property>
|
||||
<property name="headers_clickable">False</property>
|
||||
<property name="search_column">0</property>
|
||||
<property name="tooltip_column">3</property>
|
||||
<signal name="button-press-event" handler="fpr_button_pressed_cb" swapped="no"/>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection" id="treeview-selection2"/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="name_column1">
|
||||
<property name="resizable">True</property>
|
||||
<property name="title">Name</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="NameCell1"/>
|
||||
<attributes>
|
||||
<attribute name="text">1</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="trust_column1">
|
||||
<property name="resizable">True</property>
|
||||
<property name="title">Trust</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="TrustCell2"/>
|
||||
<attributes>
|
||||
<attribute name="text">2</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="fingerprint_column1">
|
||||
<property name="resizable">True</property>
|
||||
<property name="title">Fingerprint</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="FingerprintCell1"/>
|
||||
<attributes>
|
||||
<attribute name="markup">3</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkHBox" id="hbox4">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="trust_button1">
|
||||
<property name="label" translatable="yes" comments="button">Trust/Revoke Fingerprint</property>
|
||||
<property name="width_request">200</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<signal name="clicked" handler="trust_button_clicked_cb" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child type="tab">
|
||||
<object class="GtkLabel" id="label1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Own Devices</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
<property name="tab_fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkMenu" id="fprclipboard_menu">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkMenuItem" id="copyfprclipboard_item">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<signal name="activate" handler="clipboard_button_cb" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
[info]
|
||||
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.
|
||||
authors: Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de>
|
||||
Daniel Gultsch <daniel@gultsch.de>
|
||||
Philipp Hörist <philipp@hoerist.com>
|
||||
homepage: http://github.com/omemo/gajim-omemo.git
|
||||
homepage: https://dev.gajim.org/gajim/gajim-plugins/wikis/OmemoGajimPlugin
|
||||
min_gajim_version: 0.16.9
|
||||
max_gajim_version: 0.16.11
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#
|
||||
|
||||
|
||||
import sys
|
||||
import logging
|
||||
log = logging.getLogger('gajim.plugin_system.omemo')
|
||||
try:
|
||||
@@ -35,7 +36,11 @@ def encrypt(key, iv, plaintext):
|
||||
|
||||
|
||||
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):
|
||||
|
||||
@@ -29,11 +29,14 @@
|
||||
# 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.
|
||||
|
||||
import logging
|
||||
from struct import pack, unpack
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util import strxor
|
||||
|
||||
log = logging.getLogger('gajim.plugin_system.omemo')
|
||||
|
||||
|
||||
def gcm_rightshift(vec):
|
||||
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):
|
||||
""" Use AES128 GCM with the given key and iv to encrypt the payload. """
|
||||
c, t = gcm_encrypt(key, nonce, plaintext, '')
|
||||
result = c + t
|
||||
return result
|
||||
return gcm_encrypt(key, nonce, plaintext, '')
|
||||
|
||||
|
||||
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. """
|
||||
ciphertext = payload[:-16]
|
||||
mac = payload[-16:]
|
||||
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]
|
||||
key = _key
|
||||
mac = payload[-16:]
|
||||
return gcm_decrypt(key, nonce, ciphertext, '', mac)
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
|
||||
import os
|
||||
import logging
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher
|
||||
from cryptography.hazmat.primitives.ciphers import algorithms
|
||||
from cryptography.hazmat.primitives.ciphers.modes import GCM
|
||||
@@ -32,11 +33,22 @@ if os.name == 'nt':
|
||||
else:
|
||||
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. """
|
||||
data = payload[:-16]
|
||||
tag = payload[-16:]
|
||||
if len(_key) >= 32:
|
||||
# XEP-0384
|
||||
log.debug('XEP Compliant Key/Tag')
|
||||
data = payload
|
||||
key = _key[:16]
|
||||
tag = _key[16:]
|
||||
else:
|
||||
# Legacy
|
||||
log.debug('Legacy Key/Tag')
|
||||
data = payload[:-16]
|
||||
key = _key
|
||||
tag = payload[-16:]
|
||||
if os.name == 'nt':
|
||||
_backend = backend
|
||||
else:
|
||||
@@ -58,4 +70,4 @@ def aes_encrypt(key, iv, plaintext):
|
||||
algorithms.AES(key),
|
||||
GCM(iv),
|
||||
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):
|
||||
self.identityKeyStore.saveIdentity(recepientId, identityKey)
|
||||
|
||||
def deleteIdentity(self, recipientId, identityKey):
|
||||
self.identityKeyStore.deleteIdentity(recipientId, identityKey)
|
||||
|
||||
def isTrustedIdentity(self, recepientId, identityKey):
|
||||
return self.identityKeyStore.isTrustedIdentity(recepientId,
|
||||
identityKey)
|
||||
|
||||
def setTrust(self, identityKey, trust):
|
||||
return self.identityKeyStore.setTrust(identityKey, trust)
|
||||
|
||||
def getTrustedFingerprints(self, jid):
|
||||
return self.identityKeyStore.getTrustedFingerprints(jid)
|
||||
|
||||
@@ -127,6 +133,9 @@ class LiteAxolotlStore(AxolotlStore):
|
||||
# TODO Reuse this
|
||||
return self.sessionStore.getSubDeviceSessions(recepientId)
|
||||
|
||||
def getJidFromDevice(self, device_id):
|
||||
return self.sessionStore.getJidFromDevice(device_id)
|
||||
|
||||
def storeSession(self, recepientId, deviceId, sessionRecord):
|
||||
self.sessionStore.storeSession(recepientId, deviceId, sessionRecord)
|
||||
|
||||
@@ -139,6 +148,15 @@ class LiteAxolotlStore(AxolotlStore):
|
||||
def deleteAllSessions(self, recepientId):
|
||||
self.sessionStore.deleteAllSessions(recepientId)
|
||||
|
||||
def getSessionsFromJid(self, recipientId):
|
||||
return self.sessionStore.getSessionsFromJid(recipientId)
|
||||
|
||||
def getSessionsFromJids(self, recipientId):
|
||||
return self.sessionStore.getSessionsFromJids(recipientId)
|
||||
|
||||
def getAllSessions(self):
|
||||
return self.sessionStore.getAllSessions()
|
||||
|
||||
def loadSignedPreKey(self, signedPreKeyId):
|
||||
return self.signedPreKeyStore.loadSignedPreKey(signedPreKeyId)
|
||||
|
||||
|
||||
@@ -86,6 +86,13 @@ class LiteIdentityKeyStore(IdentityKeyStore):
|
||||
|
||||
return result is not None
|
||||
|
||||
def deleteIdentity(self, recipientId, identityKey):
|
||||
q = "DELETE FROM identities WHERE recipient_id = ? AND public_key = ?"
|
||||
c = self.dbConn.cursor()
|
||||
c.execute(q, (recipientId,
|
||||
identityKey.getPublicKey().serialize()))
|
||||
self.dbConn.commit()
|
||||
|
||||
def isTrustedIdentity(self, recipientId, identityKey):
|
||||
q = "SELECT trust FROM identities WHERE recipient_id = ? " \
|
||||
"AND public_key = ?"
|
||||
@@ -160,8 +167,8 @@ class LiteIdentityKeyStore(IdentityKeyStore):
|
||||
c.execute(q, fingerprints)
|
||||
self.dbConn.commit()
|
||||
|
||||
def setTrust(self, _id, trust):
|
||||
q = "UPDATE identities SET trust = ? WHERE _id = ?"
|
||||
def setTrust(self, identityKey, trust):
|
||||
q = "UPDATE identities SET trust = ? WHERE public_key = ?"
|
||||
c = self.dbConn.cursor()
|
||||
c.execute(q, (trust, _id))
|
||||
c.execute(q, (trust, identityKey.getPublicKey().serialize()))
|
||||
self.dbConn.commit()
|
||||
|
||||
@@ -48,6 +48,14 @@ class LiteSessionStore(SessionStore):
|
||||
deviceIds = [r[0] for r in result]
|
||||
return deviceIds
|
||||
|
||||
def getJidFromDevice(self, device_id):
|
||||
q = "SELECT recipient_id from sessions WHERE device_id = ?"
|
||||
c = self.dbConn.cursor()
|
||||
c.execute(q, (device_id, ))
|
||||
result = c.fetchone()
|
||||
|
||||
return result[0].decode('utf-8')
|
||||
|
||||
def getActiveDeviceTuples(self):
|
||||
q = "SELECT recipient_id, device_id FROM sessions WHERE active = 1"
|
||||
c = self.dbConn.cursor()
|
||||
@@ -82,6 +90,33 @@ class LiteSessionStore(SessionStore):
|
||||
self.dbConn.cursor().execute(q, (recipientId, ))
|
||||
self.dbConn.commit()
|
||||
|
||||
def getAllSessions(self):
|
||||
q = "SELECT _id, recipient_id, device_id, record, active from sessions"
|
||||
c = self.dbConn.cursor()
|
||||
result = []
|
||||
for row in c.execute(q):
|
||||
result.append((row[0], row[1].decode('utf-8'), row[2], row[3], row[4]))
|
||||
return result
|
||||
|
||||
def getSessionsFromJid(self, recipientId):
|
||||
q = "SELECT _id, recipient_id, device_id, record, active from sessions" \
|
||||
" WHERE recipient_id = ?"
|
||||
c = self.dbConn.cursor()
|
||||
result = []
|
||||
for row in c.execute(q, (recipientId,)):
|
||||
result.append((row[0], row[1].decode('utf-8'), row[2], row[3], row[4]))
|
||||
return result
|
||||
|
||||
def getSessionsFromJids(self, recipientId):
|
||||
q = "SELECT _id, recipient_id, device_id, record, active from sessions" \
|
||||
" WHERE recipient_id IN ({})" \
|
||||
.format(', '.join(['?'] * len(recipientId)))
|
||||
c = self.dbConn.cursor()
|
||||
result = []
|
||||
for row in c.execute(q, recipientId):
|
||||
result.append((row[0], row[1].decode('utf-8'), row[2], row[3], row[4]))
|
||||
return result
|
||||
|
||||
def setActiveState(self, deviceList, jid):
|
||||
c = self.dbConn.cursor()
|
||||
|
||||
@@ -96,28 +131,6 @@ class LiteSessionStore(SessionStore):
|
||||
c.execute(q, deviceList)
|
||||
self.dbConn.commit()
|
||||
|
||||
def getActiveSessionsKeys(self, recipientId):
|
||||
q = "SELECT record FROM sessions WHERE active = 1 AND recipient_id = ?"
|
||||
c = self.dbConn.cursor()
|
||||
result = []
|
||||
for row in c.execute(q, (recipientId,)):
|
||||
public_key = (SessionRecord(serialized=row[0]).
|
||||
getSessionState().getRemoteIdentityKey().
|
||||
getPublicKey())
|
||||
result.append(public_key.serialize())
|
||||
return result
|
||||
|
||||
def getAllActiveSessionsKeys(self):
|
||||
q = "SELECT record FROM sessions WHERE active = 1"
|
||||
c = self.dbConn.cursor()
|
||||
result = []
|
||||
for row in c.execute(q):
|
||||
public_key = (SessionRecord(serialized=row[0]).
|
||||
getSessionState().getRemoteIdentityKey().
|
||||
getPublicKey())
|
||||
result.append(public_key.serialize())
|
||||
return result
|
||||
|
||||
def getInactiveSessionsKeys(self, recipientId):
|
||||
q = "SELECT record FROM sessions WHERE active = 0 AND recipient_id = ?"
|
||||
c = self.dbConn.cursor()
|
||||
|
||||
@@ -29,6 +29,14 @@ class SQLDatabase():
|
||||
self.dbConn = dbConn
|
||||
self.createDb()
|
||||
self.migrateDb()
|
||||
c = self.dbConn.cursor()
|
||||
c.execute("PRAGMA synchronous=NORMAL;")
|
||||
c.execute("PRAGMA journal_mode;")
|
||||
mode = c.fetchone()[0]
|
||||
# WAL is a persistent DB mode, dont override it if user has set it
|
||||
if mode != 'wal':
|
||||
c.execute("PRAGMA journal_mode=MEMORY;")
|
||||
self.dbConn.commit()
|
||||
|
||||
def createDb(self):
|
||||
if user_version(self.dbConn) == 0:
|
||||
|
||||
@@ -200,8 +200,8 @@ class OmemoState:
|
||||
key = self.handleWhisperMessage(sender_jid, sid, encrypted_key)
|
||||
except (NoSessionException, InvalidMessageException) as e:
|
||||
log.warning('No Session found ' + e.message)
|
||||
log.warning('sender_jid => ' + str(sender_jid) +
|
||||
' sid =>' + sid)
|
||||
log.warning('sender_jid => ' + str(sender_jid) + ' sid =>' +
|
||||
str(sid))
|
||||
return
|
||||
except (DuplicateMessageException) as e:
|
||||
log.warning('Duplicate message found ' + str(e.args))
|
||||
@@ -211,7 +211,7 @@ class OmemoState:
|
||||
log.warning('Duplicate message found ' + str(e.args))
|
||||
return
|
||||
|
||||
result = decrypt(key, iv, payload).decode('utf-8')
|
||||
result = decrypt(key, iv, payload)
|
||||
|
||||
log.debug("Decrypted Message => " + result)
|
||||
return result
|
||||
@@ -226,43 +226,44 @@ class OmemoState:
|
||||
log.error('No known devices')
|
||||
return
|
||||
|
||||
for dev in devices_list:
|
||||
self.get_session_cipher(jid, dev)
|
||||
session_ciphers = self.session_ciphers[jid]
|
||||
if not session_ciphers:
|
||||
log.warning('No session ciphers for ' + jid)
|
||||
return
|
||||
payload, tag = encrypt(key, iv, plaintext)
|
||||
|
||||
# for XEP-384 Compliance uncomment
|
||||
# key += tag
|
||||
payload += tag
|
||||
|
||||
# Encrypt the message key with for each of receivers devices
|
||||
for rid, cipher in session_ciphers.items():
|
||||
for device in devices_list:
|
||||
try:
|
||||
if self.isTrusted(cipher) == TRUSTED:
|
||||
encrypted_keys[rid] = cipher.encrypt(key).serialize()
|
||||
if self.isTrusted(jid, device) == TRUSTED:
|
||||
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:
|
||||
log.debug('Skipped Device because Trust is: ' +
|
||||
str(self.isTrusted(cipher)))
|
||||
str(self.isTrusted(jid, device)))
|
||||
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:
|
||||
log_msg = 'Encrypted keys empty'
|
||||
log.error(log_msg)
|
||||
raise NoValidSessions(log_msg)
|
||||
log.error('Encrypted keys empty')
|
||||
raise NoValidSessions('Encrypted keys empty')
|
||||
|
||||
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:
|
||||
for device in my_other_devices:
|
||||
try:
|
||||
cipher = self.get_session_cipher(from_jid, dev)
|
||||
if self.isTrusted(cipher) == TRUSTED:
|
||||
encrypted_keys[dev] = cipher.encrypt(key).serialize()
|
||||
if self.isTrusted(from_jid, device) == TRUSTED:
|
||||
cipher = self.get_session_cipher(from_jid, device)
|
||||
cipher_key = cipher.encrypt(key)
|
||||
prekey = isinstance(cipher_key, PreKeyWhisperMessage)
|
||||
encrypted_keys[device] = (cipher_key.serialize(), prekey)
|
||||
else:
|
||||
log.debug('Skipped own Device because Trust is: ' +
|
||||
str(self.isTrusted(cipher)))
|
||||
str(self.isTrusted(from_jid, device)))
|
||||
except:
|
||||
log.warning('Failed to find key for device ' + str(dev))
|
||||
|
||||
payload = encrypt(key, iv, plaintext)
|
||||
log.warning('Failed to find key for device ' + str(device))
|
||||
|
||||
result = {'sid': self.own_device_id,
|
||||
'keys': encrypted_keys,
|
||||
@@ -273,14 +274,109 @@ class OmemoState:
|
||||
log.debug('Finished encrypting message')
|
||||
return result
|
||||
|
||||
def isTrusted(self, cipher):
|
||||
self.cipher = cipher
|
||||
self.state = self.cipher.sessionStore. \
|
||||
loadSession(self.cipher.recipientId, self.cipher.deviceId). \
|
||||
getSessionState()
|
||||
self.key = self.state.getRemoteIdentityKey()
|
||||
return self.store.identityKeyStore. \
|
||||
isTrustedIdentity(self.cipher.recipientId, self.key)
|
||||
def create_gc_msg(self, from_jid, jid, plaintext):
|
||||
key = get_random_bytes(16)
|
||||
iv = get_random_bytes(16)
|
||||
encrypted_keys = {}
|
||||
room = jid
|
||||
encrypted_jids = []
|
||||
|
||||
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):
|
||||
inactive = self.store.getInactiveSessionsKeys(recipient_id)
|
||||
@@ -296,20 +392,6 @@ class OmemoState:
|
||||
|
||||
return undecided
|
||||
|
||||
def device_list_for(self, jid):
|
||||
""" Return a list of known device ids for the specified jid.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
jid : string
|
||||
The contacts jid
|
||||
"""
|
||||
if jid == self.own_jid:
|
||||
return set(self.own_devices) - set({self.own_device_id})
|
||||
if jid not in self.device_ids:
|
||||
return set()
|
||||
return set(self.device_ids[jid])
|
||||
|
||||
def devices_without_sessions(self, jid):
|
||||
""" List device_ids for the given jid which have no axolotl session.
|
||||
|
||||
@@ -364,10 +446,10 @@ class OmemoState:
|
||||
|
||||
def handleWhisperMessage(self, recipient_id, device_id, key):
|
||||
whisperMessage = WhisperMessage(serialized=key)
|
||||
sessionCipher = self.get_session_cipher(recipient_id, device_id)
|
||||
log.debug(self.account + " => Received WhisperMessage from " +
|
||||
recipient_id)
|
||||
if self.isTrusted(sessionCipher) >= TRUSTED:
|
||||
if self.isTrusted(recipient_id, device_id):
|
||||
sessionCipher = self.get_session_cipher(recipient_id, device_id)
|
||||
key = sessionCipher.decryptMsg(whisperMessage, textMsg=False)
|
||||
return key
|
||||
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
|
||||
601
omemo/ui.py
601
omemo/ui.py
@@ -20,18 +20,30 @@
|
||||
|
||||
import binascii
|
||||
import logging
|
||||
import os
|
||||
import message_control
|
||||
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import GObject, Gtk, GdkPixbuf
|
||||
|
||||
# pylint: disable=import-error
|
||||
import gtkgui_helpers
|
||||
from common import gajim
|
||||
from dialogs import YesNoDialog
|
||||
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')
|
||||
|
||||
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
|
||||
TRUSTED = 1
|
||||
UNTRUSTED = 0
|
||||
@@ -106,6 +118,12 @@ class Ui(object):
|
||||
self.account = self.contact.account.name
|
||||
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.refresh_auth_lock_icon()
|
||||
|
||||
@@ -133,6 +151,9 @@ class Ui(object):
|
||||
item.set_image(Gtk.Image.new_from_file(icon_path))
|
||||
item.set_submenu(submenu)
|
||||
|
||||
if self.groupchat:
|
||||
item.set_sensitive(self.omemo_capable)
|
||||
|
||||
# at index 8 is the separator after the esession encryption entry
|
||||
menu.insert(item, 8)
|
||||
return menu
|
||||
@@ -159,7 +180,38 @@ class Ui(object):
|
||||
log.debug(self.account + ' => Sending Message to ' +
|
||||
self.contact.jid)
|
||||
|
||||
self.chat_control.send_message = omemo_send_message
|
||||
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
|
||||
|
||||
def set_omemo_state(self, enabled):
|
||||
"""
|
||||
@@ -182,6 +234,12 @@ class Ui(object):
|
||||
self.omemobutton.set_omemo_state(enabled)
|
||||
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):
|
||||
return self.state.encryption.is_active(self.contact.jid)
|
||||
|
||||
@@ -190,16 +248,31 @@ class Ui(object):
|
||||
self.set_omemo_state(True)
|
||||
|
||||
def new_fingerprints_available(self):
|
||||
fingerprints = self.state.store.getNewFingerprints(self.contact.jid)
|
||||
if fingerprints:
|
||||
self.show_fingerprint_window(fingerprints)
|
||||
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:
|
||||
self.show_fingerprint_window(fingerprints)
|
||||
|
||||
def show_fingerprint_window(self, fingerprints=None):
|
||||
if 'dialog' not in self.windowinstances:
|
||||
self.windowinstances['dialog'] = \
|
||||
FingerprintWindow(self.plugin, self.contact,
|
||||
self.chat_control.parent_win.window,
|
||||
self.windowinstances)
|
||||
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'] = \
|
||||
FingerprintWindow(self.plugin, self.contact,
|
||||
self.chat_control.parent_win.window,
|
||||
self.windowinstances)
|
||||
self.windowinstances['dialog'].show_all()
|
||||
if fingerprints:
|
||||
log.debug(self.account +
|
||||
@@ -225,10 +298,12 @@ class Ui(object):
|
||||
|
||||
def no_trusted_fingerprints_warning(self):
|
||||
msg = "To send an encrypted message, you have to " \
|
||||
"first trust the fingerprint of your contact!"
|
||||
"first trust the fingerprint of your contact!"
|
||||
self.chat_control.print_conversation_line(msg, 'status', '', None)
|
||||
|
||||
def refresh_auth_lock_icon(self):
|
||||
if self.groupchat:
|
||||
return
|
||||
if self.encryption_active():
|
||||
if self.state.getUndecidedFingerprints(self.contact.jid):
|
||||
self.chat_control._show_lock_image(True, 'OMEMO', True, True,
|
||||
@@ -242,6 +317,8 @@ class Ui(object):
|
||||
|
||||
def removeUi(self):
|
||||
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.omemo_orig_prepare_context_menu
|
||||
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.add_from_file(self.GTK_BUILDER_FILE_PATH)
|
||||
|
||||
self.fpr_model = Gtk.ListStore(GObject.TYPE_INT,
|
||||
GObject.TYPE_STRING,
|
||||
GObject.TYPE_STRING,
|
||||
GObject.TYPE_STRING)
|
||||
try:
|
||||
self.disabled_accounts = self.plugin.config['DISABLED_ACCOUNTS']
|
||||
except KeyError:
|
||||
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')
|
||||
|
||||
for account in sorted(gajim.contacts.get_accounts()):
|
||||
self.account_store.append(row=(account,))
|
||||
self.fpr_model = self.B.get_object('fingerprint_store')
|
||||
self.device_model = self.B.get_object('deviceid_store')
|
||||
|
||||
self.fpr_view = self.B.get_object('fingerprint_view')
|
||||
self.fpr_view.set_model(self.fpr_model)
|
||||
self.fpr_view.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE)
|
||||
|
||||
self.device_view = self.B.get_object('deviceid_view')
|
||||
self.device_view.set_model(self.device_model)
|
||||
self.disabled_acc_store = self.B.get_object('disabled_account_store')
|
||||
self.account_store = self.B.get_object('account_store')
|
||||
|
||||
if len(self.account_store) > 0:
|
||||
self.B.get_object('account_combobox').set_active(0)
|
||||
self.active_acc_view = self.B.get_object('active_accounts_view')
|
||||
self.disabled_acc_view = self.B.get_object('disabled_accounts_view')
|
||||
|
||||
vbox = self.get_content_area()
|
||||
vbox.pack_start(self.B.get_object('notebook1'), True, True, 0)
|
||||
|
||||
self.B.connect_signals(self)
|
||||
|
||||
self.plugin_active = False
|
||||
|
||||
def on_run(self):
|
||||
self.update_context_list()
|
||||
self.account_combobox_changed_cb(self.B.get_object('account_combobox'))
|
||||
for plugin in gajim.plugin_manager.active_plugins:
|
||||
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):
|
||||
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):
|
||||
active = self.B.get_object('account_combobox').get_active()
|
||||
account = self.account_store[active][0]
|
||||
@@ -298,43 +479,42 @@ class OMEMOConfigDialog(GajimPluginConfigDialog):
|
||||
|
||||
mod, paths = self.fpr_view.get_selection().get_selected_rows()
|
||||
|
||||
def on_yes(checked, identity_key):
|
||||
state.store.setTrust(identity_key, TRUSTED)
|
||||
try:
|
||||
if 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()
|
||||
|
||||
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)
|
||||
_id, user, fpr = mod.get(it, 0, 1, 3)
|
||||
jid, fpr, deviceid = mod.get(it, 1, 3, 4)
|
||||
fpr = fpr[31:-12]
|
||||
dlg = Gtk.Dialog('Trust / Revoke Fingerprint', self,
|
||||
Gtk.DialogFlags.MODAL |
|
||||
Gtk.DialogFlags.DESTROY_WITH_PARENT,
|
||||
(Gtk.STOCK_YES, Gtk.ResponseType.YES,
|
||||
Gtk.STOCK_NO, Gtk.ResponseType.NO))
|
||||
l = Gtk.Label()
|
||||
l.set_markup('Do you want to trust the '
|
||||
'fingerprint of <b>%s</b> on your account <b>%s</b>?'
|
||||
'\n\n<tt>%s</tt>' % (user, account, fpr))
|
||||
l.set_line_wrap(True)
|
||||
l.set_padding(12, 12)
|
||||
vbox = dlg.get_content_area()
|
||||
vbox.add(l)
|
||||
dlg.show_all()
|
||||
|
||||
response = dlg.run()
|
||||
if response == Gtk.ResponseType.YES:
|
||||
state.store.identityKeyStore.setTrust(_id, TRUSTED)
|
||||
try:
|
||||
if self.plugin.ui_list[account]:
|
||||
self.plugin.ui_list[account][user].refresh_auth_lock_icon()
|
||||
except:
|
||||
dlg.destroy()
|
||||
else:
|
||||
if response == Gtk.ResponseType.NO:
|
||||
state.store.identityKeyStore.setTrust(_id, UNTRUSTED)
|
||||
try:
|
||||
if user in self.plugin.ui_list[account]:
|
||||
self.plugin.ui_list[account][user].refresh_auth_lock_icon()
|
||||
except:
|
||||
dlg.destroy()
|
||||
record = state.store.loadSession(jid, deviceid)
|
||||
identity_key = record.getSessionState().getRemoteIdentityKey()
|
||||
|
||||
self.update_context_list()
|
||||
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):
|
||||
active = self.B.get_object('account_combobox').get_active()
|
||||
@@ -380,67 +560,94 @@ class OMEMOConfigDialog(GajimPluginConfigDialog):
|
||||
def update_context_list(self):
|
||||
self.fpr_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()
|
||||
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
|
||||
self.B.get_object('ID').set_markup('<tt>%s</tt>' % deviceid)
|
||||
|
||||
ownfpr = binascii.hexlify(state.store.getIdentityKeyPair()
|
||||
.getPublicKey().serialize()).decode('utf-8')
|
||||
ownfpr = self.human_hash(ownfpr[2:])
|
||||
human_ownfpr = human_hash(ownfpr[2:])
|
||||
self.B.get_object('fingerprint_label').set_markup('<tt>%s</tt>'
|
||||
% ownfpr)
|
||||
% human_ownfpr)
|
||||
|
||||
fprDB = state.store.identityKeyStore.getAllFingerprints()
|
||||
activeSessions = state.store.sessionStore. \
|
||||
getAllActiveSessionsKeys()
|
||||
for item in fprDB:
|
||||
_id, jid, fpr, tr = item
|
||||
active = fpr in activeSessions
|
||||
fpr = binascii.hexlify(fpr).decode('utf-8')
|
||||
fpr = self.human_hash(fpr[2:])
|
||||
jid = jid.decode('utf-8')
|
||||
if tr == UNTRUSTED:
|
||||
if active:
|
||||
self.fpr_model.append((_id, jid, 'False',
|
||||
'<tt><span foreground="#FF0040">%s</span></tt>' % fpr))
|
||||
else:
|
||||
self.fpr_model.append((_id, jid, 'False',
|
||||
'<tt><span foreground="#585858">%s</span></tt>' % fpr))
|
||||
elif tr == TRUSTED:
|
||||
if active:
|
||||
self.fpr_model.append((_id, jid, 'True',
|
||||
'<tt><span foreground="#2EFE2E">%s</span></tt>' % fpr))
|
||||
else:
|
||||
self.fpr_model.append((_id, jid, 'True',
|
||||
'<tt><span foreground="#585858">%s</span></tt>' % fpr))
|
||||
else:
|
||||
if active:
|
||||
self.fpr_model.append((_id, jid, 'Undecided',
|
||||
'<tt><span foreground="#FF8000">%s</span></tt>' % fpr))
|
||||
else:
|
||||
self.fpr_model.append((_id, jid, 'Undecided',
|
||||
'<tt><span foreground="#585858">%s</span></tt>' % fpr))
|
||||
# Set Fingerprint List
|
||||
trust_str = {0: 'False', 1: 'True', 2: 'Undecided'}
|
||||
session_db = state.store.getAllSessions()
|
||||
|
||||
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:
|
||||
self.device_model.append([item])
|
||||
|
||||
def human_hash(self, fpr):
|
||||
fpr = fpr.upper()
|
||||
fplen = len(fpr)
|
||||
wordsize = fplen // 8
|
||||
buf = ''
|
||||
for w in range(0, fplen, wordsize):
|
||||
buf += '{0} '.format(fpr[w:w + wordsize])
|
||||
return buf.rstrip()
|
||||
# Set QR Verification Code
|
||||
if PILLOW:
|
||||
path = self.get_qrcode(
|
||||
gajim.get_jid_from_account(account), deviceid, ownfpr[2:])
|
||||
self.qrcode.set_from_pixbuf(GdkPixbuf.Pixbuf.new_from_file(path))
|
||||
self.qrinfo.hide()
|
||||
else:
|
||||
self.qrinfo.show()
|
||||
|
||||
|
||||
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.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,
|
||||
title=('Fingerprints for %s') % contact.jid,
|
||||
parent=parent,
|
||||
@@ -448,45 +655,34 @@ class FingerprintWindow(Gtk.Dialog):
|
||||
close_button = self.add_button(Gtk.STOCK_CLOSE, Gtk.ResponseType.CLOSE)
|
||||
close_button.connect('clicked', self.on_close_button_clicked)
|
||||
self.connect('delete-event', self.on_window_delete)
|
||||
self.plugin = plugin
|
||||
|
||||
self.GTK_BUILDER_FILE_PATH = \
|
||||
self.plugin.local_file_path('fpr_dialog.ui')
|
||||
self.B = Gtk.Builder()
|
||||
self.B.set_translation_domain('gajim_plugins')
|
||||
self.B.add_from_file(self.GTK_BUILDER_FILE_PATH)
|
||||
self.xml = Gtk.Builder()
|
||||
self.xml.add_from_file(self.GTK_BUILDER_FILE_PATH)
|
||||
self.xml.set_translation_domain('gajim_plugins')
|
||||
|
||||
self.fpr_model = Gtk.ListStore(GObject.TYPE_INT,
|
||||
GObject.TYPE_STRING,
|
||||
GObject.TYPE_STRING,
|
||||
GObject.TYPE_STRING)
|
||||
self.fpr_model = self.xml.get_object('fingerprint_store')
|
||||
|
||||
self.fpr_view = self.B.get_object('fingerprint_view')
|
||||
self.fpr_view.set_model(self.fpr_model)
|
||||
self.fpr_view.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE)
|
||||
|
||||
self.fpr_view_own = self.B.get_object('fingerprint_view_own')
|
||||
self.fpr_view_own.set_model(self.fpr_model)
|
||||
self.fpr_view_own.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE)
|
||||
|
||||
self.notebook = self.B.get_object('notebook1')
|
||||
self.fpr_view = self.xml.get_object('fingerprint_view')
|
||||
self.fpr_view_own = self.xml.get_object('fingerprint_view_own')
|
||||
|
||||
self.notebook = self.xml.get_object('notebook1')
|
||||
vbox = self.get_content_area()
|
||||
vbox.pack_start(self.notebook, True, True, 0)
|
||||
|
||||
self.B.connect_signals(self)
|
||||
|
||||
self.account = self.contact.account.name
|
||||
self.omemostate = self.plugin.get_omemo_state(self.account)
|
||||
self.xml.connect_signals(self)
|
||||
|
||||
# Set own Fingerprint Label
|
||||
ownfpr = binascii.hexlify(self.omemostate.store.getIdentityKeyPair()
|
||||
.getPublicKey().serialize()).decode('utf-8')
|
||||
ownfpr = self.human_hash(ownfpr[2:])
|
||||
|
||||
self.B.get_object('fingerprint_label_own').set_markup('<tt>%s</tt>'
|
||||
% ownfpr)
|
||||
|
||||
ownfpr = human_hash(ownfpr[2:])
|
||||
self.xml.get_object('fingerprint_label_own').set_markup('<tt>%s</tt>'
|
||||
% ownfpr)
|
||||
self.update_context_list()
|
||||
|
||||
self.show_all()
|
||||
|
||||
def on_close_button_clicked(self, widget):
|
||||
del self.windowinstances['dialog']
|
||||
self.hide()
|
||||
@@ -496,43 +692,43 @@ class FingerprintWindow(Gtk.Dialog):
|
||||
self.hide()
|
||||
|
||||
def trust_button_clicked_cb(self, button, *args):
|
||||
state = self.omemostate
|
||||
|
||||
if self.notebook.get_current_page() == 1:
|
||||
mod, paths = self.fpr_view_own.get_selection().get_selected_rows()
|
||||
else:
|
||||
mod, paths = self.fpr_view.get_selection().get_selected_rows()
|
||||
|
||||
for path in paths:
|
||||
it = mod.get_iter(path)
|
||||
_id, user, fpr = mod.get(it, 0, 1, 3)
|
||||
fpr = fpr[31:-12]
|
||||
dlg = Gtk.Dialog('Trust / Revoke Fingerprint', self,
|
||||
Gtk.DialogFlags.MODAL |
|
||||
Gtk.DialogFlags.DESTROY_WITH_PARENT,
|
||||
(Gtk.STOCK_YES, Gtk.ResponseType.YES,
|
||||
Gtk.STOCK_NO, Gtk.ResponseType.NO))
|
||||
l = Gtk.Label()
|
||||
l.set_markup('Do you want to trust the '
|
||||
'fingerprint of <b>%s</b> on your account <b>%s</b>?'
|
||||
'\n\n<tt>%s</tt>' % (user, self.account, fpr))
|
||||
l.set_line_wrap(True)
|
||||
l.set_padding(12, 12)
|
||||
vbox = dlg.get_content_area()
|
||||
vbox.add(l)
|
||||
dlg.show_all()
|
||||
response = dlg.run()
|
||||
if response == Gtk.ResponseType.YES:
|
||||
self.omemostate.store.identityKeyStore.setTrust(_id, TRUSTED)
|
||||
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()
|
||||
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()
|
||||
|
||||
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:
|
||||
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, 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):
|
||||
if event.button == 3:
|
||||
@@ -547,7 +743,7 @@ class FingerprintWindow(Gtk.Dialog):
|
||||
# selection, otherwise we only select the new item
|
||||
keep_selection = tw.get_selection().path_is_selected(pthinfo[0])
|
||||
|
||||
pop = self.B.get_object('fprclipboard_menu')
|
||||
pop = self.xml.get_object('fprclipboard_menu')
|
||||
pop.popup(None, None, None, event.button, event.time)
|
||||
|
||||
# keep_selection=True -> no further processing of click event
|
||||
@@ -571,49 +767,56 @@ class FingerprintWindow(Gtk.Dialog):
|
||||
|
||||
def update_context_list(self, *args):
|
||||
self.fpr_model.clear()
|
||||
state = self.omemostate
|
||||
|
||||
if self.notebook.get_current_page() == 1:
|
||||
jid = gajim.get_jid_from_account(self.account)
|
||||
contact_jid = self.own_jid
|
||||
else:
|
||||
jid = self.contact.jid
|
||||
contact_jid = self.contact.jid
|
||||
|
||||
fprDB = self.omemostate.store.identityKeyStore.getFingerprints(jid)
|
||||
activeSessions = self.omemostate.store.sessionStore. \
|
||||
getActiveSessionsKeys(jid)
|
||||
trust_str = {0: 'False', 1: 'True', 2: 'Undecided'}
|
||||
if self.groupchat and self.notebook.get_current_page() == 0:
|
||||
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:
|
||||
_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
|
||||
|
||||
def human_hash(self, fpr):
|
||||
fpr = fpr.upper()
|
||||
fplen = len(fpr)
|
||||
wordsize = fplen // 8
|
||||
buf = ''
|
||||
for w in range(0, fplen, wordsize):
|
||||
buf += '{0} '.format(fpr[w:w + wordsize])
|
||||
return buf.rstrip()
|
||||
_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()
|
||||
fplen = len(fpr)
|
||||
wordsize = fplen // 8
|
||||
buf = ''
|
||||
for w in range(0, fplen, wordsize):
|
||||
buf += '{0} '.format(fpr[w:w + wordsize])
|
||||
return buf.rstrip()
|
||||
|
||||
@@ -79,10 +79,14 @@ class OmemoMessage(Node):
|
||||
# , contact_jid, key, iv, payload, dev_id, my_dev_id):
|
||||
Node.__init__(self, 'encrypted', attrs={'xmlns': NS_OMEMO})
|
||||
header = Node('header', attrs={'sid': msg_dict['sid']})
|
||||
for rid, key in msg_dict['keys'].items():
|
||||
header.addChild('key', attrs={'rid': rid}).addData(b64encode(key)
|
||||
.decode('utf-8'))
|
||||
|
||||
for rid, (key, prekey) in msg_dict['keys'].items():
|
||||
if prekey:
|
||||
child = header.addChild('key',
|
||||
attrs={'prekey': 'true', 'rid': rid})
|
||||
else:
|
||||
child = header.addChild('key',
|
||||
attrs={'rid': rid})
|
||||
child.addData(b64encode(key).decode('utf-8'))
|
||||
header.addChild('iv').addData(b64encode(msg_dict['iv']).decode('utf-8'))
|
||||
self.addChild(node=header)
|
||||
self.addChild('payload').addData(b64encode(msg_dict['payload'])
|
||||
|
||||
Reference in New Issue
Block a user