Welcome to mirror list, hosted at ThFree Co, Russian Federation.

dev.gajim.org/gajim/gajim-plugins.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhilipp Hörist <forenjunkie@chello.at>2017-03-26 17:37:59 +0300
committerPhilipp Hörist <forenjunkie@chello.at>2017-03-26 17:37:59 +0300
commit6ace7341378f11d83d2685ae85fb938b7bc5a463 (patch)
tree5f334932a4203980c9bbbdb099363022d406332d
parent573c88a3c58dfe8122e8f4882cace2b41f9cd210 (diff)
parente72763eefc79a7cf050e15c9d8497004db00e721 (diff)
Merge branch 'master' into 'master'
OMEMO Version 1.1.0 See merge request !31
-rw-r--r--omemo/CHANGELOG3
-rw-r--r--omemo/file_decryption.py286
-rw-r--r--omemo/manifest.ini2
-rw-r--r--omemo/omemoplugin.py2
-rw-r--r--omemo/upload_progress_dialog.ui108
5 files changed, 400 insertions, 1 deletions
diff --git a/omemo/CHANGELOG b/omemo/CHANGELOG
index a7d5817..9558f0f 100644
--- a/omemo/CHANGELOG
+++ b/omemo/CHANGELOG
@@ -1,3 +1,6 @@
+1.1.0 / 2017-03-26
+- Add file decryption
+
1.0.4 / 2017-03-01
- Use correct tag name for EME
diff --git a/omemo/file_decryption.py b/omemo/file_decryption.py
new file mode 100644
index 0000000..8f80471
--- /dev/null
+++ b/omemo/file_decryption.py
@@ -0,0 +1,286 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright 2017 Philipp Hörist <philipp@hoerist.com>
+#
+# This file is part of Gajim-OMEMO plugin.
+#
+# The Gajim-OMEMO plugin is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option) any
+# later version.
+#
+# Gajim-OMEMO is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# the Gajim-OMEMO plugin. If not, see <http://www.gnu.org/licenses/>.
+#
+
+import os
+import hashlib
+import logging
+import urllib2
+import socket
+import threading
+import platform
+import subprocess
+import binascii
+from io import BufferedWriter, FileIO, BytesIO
+from urlparse import urlparse, urldefrag
+
+import gtk
+import gobject
+import gtkgui_helpers
+from common import configpaths
+from dialogs import ErrorDialog, YesNoDialog
+
+from cryptography.hazmat.primitives.ciphers import Cipher
+from cryptography.hazmat.primitives.ciphers import algorithms
+from cryptography.hazmat.primitives.ciphers.modes import GCM
+from cryptography.hazmat.backends import default_backend
+
+log = logging.getLogger('gajim.plugin_system.omemo.filedecryption')
+
+DIRECTORY = os.path.join(configpaths.gajimpaths['MY_DATA'], 'downloads')
+try:
+ if not os.path.exists(DIRECTORY):
+ os.makedirs(DIRECTORY)
+ ERROR = False
+except Exception:
+ ERROR = True
+ log.exception('Error')
+
+
+class File:
+ def __init__(self, url):
+ self.url, self.fragment = urldefrag(url)
+ self.key = None
+ self.iv = None
+ self.filepath = None
+ self.filename = None
+
+
+class FileDecryption:
+ def __init__(self, plugin):
+ self.plugin = plugin
+ self.chat_control = None
+ self.orig_handler = None
+ self.tv = None
+ self.progress_windows = {}
+
+ def activate(self, chat_control):
+ if ERROR:
+ return
+ self.tv = chat_control.conv_textview.tv
+ self.chat_control = chat_control
+ self.orig_handler = self.tv.hyperlink_handler
+ self.tv.hyperlink_handler = self.hyperlink_handler
+
+ def deactivate(self):
+ self.tv.hyperlink_handler = self.orig_handler
+
+ def hyperlink_handler(self, texttag, widget, event, iter_, kind):
+ if event.type != gtk.gdk.BUTTON_PRESS:
+ return
+ begin_iter = iter_.copy()
+ # we get the begining of the tag
+ while not begin_iter.begins_tag(texttag):
+ begin_iter.backward_char()
+ end_iter = iter_.copy()
+ # we get the end of the tag
+ while not end_iter.ends_tag(texttag):
+ end_iter.forward_char()
+
+ url = self.tv.get_buffer().get_text(begin_iter, end_iter, True)
+ urlparts = urlparse(url)
+ file = File(urlparts.geturl())
+
+ if urlparts.scheme not in ["https"] or not urlparts.netloc:
+ log.info("Not accepting URL for decryption: %s", url)
+ self.orig_handler(texttag, widget, event, iter_, kind)
+ return
+
+ if not self.is_encrypted(file):
+ log.info('Url not encrypted: %s', url)
+ self.orig_handler(texttag, widget, event, iter_, kind)
+ return
+
+ self.create_paths(file)
+
+ if os.path.exists(file.filepath):
+ self.finished(file)
+ return
+
+ event = threading.Event()
+ progressbar = ProgressWindow(self.plugin, self.chat_control, event)
+ thread = threading.Thread(target=Download,
+ args=(file, progressbar, self.chat_control,
+ event, self))
+ thread.daemon = True
+ thread.start()
+
+ def is_encrypted(self, file):
+ if file.fragment:
+ try:
+ fragment = binascii.unhexlify(file.fragment)
+ file.key = fragment[16:]
+ file.iv = fragment[:16]
+ if len(file.key) == 32 and len(file.iv) == 16:
+ return True
+ except:
+ return False
+ return False
+
+ def create_paths(self, file):
+ file.filename = os.path.basename(file.url)
+ ext = os.path.splitext(file.filename)[1]
+ name = os.path.splitext(file.filename)[0]
+ urlhash = hashlib.sha1(file.url).hexdigest()
+ newfilename = name + '_' + urlhash[:10] + ext
+ file.filepath = os.path.join(DIRECTORY, newfilename)
+
+ def finished(self, file):
+ question = 'Do you want to open %s' % file.filename
+ YesNoDialog('Open File', question,
+ transient_for=self.chat_control.parent_win.window,
+ on_response_yes=(self.open_file, file.filepath))
+ return False
+
+ def open_file(self, checked, path):
+ if platform.system() == "Windows":
+ os.startfile(path)
+ elif platform.system() == "Darwin":
+ subprocess.Popen(["open", path])
+ else:
+ subprocess.Popen(["xdg-open", path])
+
+
+class Download:
+ def __init__(self, file, progressbar, chat_control, event, base):
+ self.file = file
+ self.progressbar = progressbar
+ self.chat_control = chat_control
+ self.event = event
+ self.base = base
+ self.download()
+
+ def download(self):
+ gobject.idle_add(self.progressbar.set_text, 'Downloading...')
+ data = self.load_url()
+ if isinstance(data, str):
+ gobject.idle_add(self.progressbar.close_dialog)
+ gobject.idle_add(self.error, data)
+ return
+
+ gobject.idle_add(self.progressbar.set_text, 'Decrypting...')
+ decrypted_data = self.aes_decrypt(data)
+
+ gobject.idle_add(
+ self.progressbar.set_text, 'Writing file to harddisk...')
+ self.write_file(decrypted_data)
+
+ gobject.idle_add(self.progressbar.close_dialog)
+
+ gobject.idle_add(self.base.finished, self.file)
+
+ def load_url(self):
+ try:
+ get_request = urllib2.urlopen(self.file.url, timeout=30)
+ size = get_request.info().getheader('Content-Length').strip()
+ if not size:
+ errormsg = 'Content-Length not found in header'
+ log.error(errormsg)
+ return errormsg
+ stream = BytesIO()
+ while True:
+ try:
+ if self.event.isSet():
+ raise UploadAbortedException
+ temp = get_request.read(10000)
+ gobject.idle_add(
+ self.progressbar.update_progress, len(temp), size)
+ except socket.timeout:
+ errormsg = 'Request timeout'
+ log.error(errormsg)
+ return errormsg
+ if temp:
+ stream.write(temp)
+ else:
+ return stream
+ except UploadAbortedException as error:
+ log.info('Upload Aborted')
+ errormsg = error
+ except urllib2.URLError as error:
+ log.exception('URLError')
+ errormsg = error.reason
+ except Exception as error:
+ log.exception('Error')
+ errormsg = error
+ stream.close()
+ return str(errormsg)
+
+ def aes_decrypt(self, payload):
+ # Use AES128 GCM with the given key and iv to decrypt the payload.
+ payload = payload.getvalue()
+ data = payload[:-16]
+ tag = payload[-16:]
+ decryptor = Cipher(
+ algorithms.AES(self.file.key),
+ GCM(self.file.iv, tag=tag),
+ backend=default_backend()).decryptor()
+ return decryptor.update(data) + decryptor.finalize()
+
+ def write_file(self, data):
+ log.info('Writing data to %s', self.file.filepath)
+ try:
+ with BufferedWriter(FileIO(self.file.filepath, "wb")) as output:
+ output.write(data)
+ output.close()
+ except Exception:
+ log.exception('Failed to write file')
+
+ def error(self, error):
+ ErrorDialog(
+ _('Error'), error,
+ transient_for=self.chat_control.parent_win.window)
+ return False
+
+
+class ProgressWindow:
+ def __init__(self, plugin, chat_control, event):
+ self.plugin = plugin
+ self.event = event
+ self.xml = gtkgui_helpers.get_gtk_builder(
+ self.plugin.local_file_path('upload_progress_dialog.ui'))
+ self.dialog = self.xml.get_object('progress_dialog')
+ self.dialog.set_transient_for(chat_control.parent_win.window)
+ self.label = self.xml.get_object('label')
+ self.progressbar = self.xml.get_object('progressbar')
+ self.progressbar.set_text("")
+ self.dialog.show_all()
+ self.xml.connect_signals(self)
+ self.seen = 0
+
+ def set_text(self, text):
+ self.label.set_markup('<big>%s</big>' % text)
+ return False
+
+ def update_progress(self, seen, total):
+ self.seen += seen
+ pct = (self.seen / float(total)) * 100.0
+ self.progressbar.set_fraction(self.seen / float(total))
+ self.progressbar.set_text(str(int(pct)) + "%")
+ return False
+
+ def close_dialog(self, *args):
+ self.dialog.destroy()
+ return False
+
+ def on_destroy(self, *args):
+ self.event.set()
+
+
+class UploadAbortedException(Exception):
+ def __str__(self):
+ return _('Upload Aborted')
diff --git a/omemo/manifest.ini b/omemo/manifest.ini
index ab38078..9696093 100644
--- a/omemo/manifest.ini
+++ b/omemo/manifest.ini
@@ -1,7 +1,7 @@
[info]
name: OMEMO
short_name: omemo
-version: 1.0.4
+version: 1.1.0
description: OMEMO is an XMPP Extension Protocol (XEP) for secure multi-client end-to-end encryption based on Axolotl and PEP. You need to install some dependencys, you can find install instructions for your system in the Gitlab Wiki.
authors: Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de>
Daniel Gultsch <daniel@gultsch.de>
diff --git a/omemo/omemoplugin.py b/omemo/omemoplugin.py
index 03c06c5..9b8167a 100644
--- a/omemo/omemoplugin.py
+++ b/omemo/omemoplugin.py
@@ -37,6 +37,7 @@ from .xmpp import (
DevicelistPEP, OmemoMessage, successful, unpack_device_bundle,
unpack_device_list_update, unpack_encrypted)
+from .file_decryption import FileDecryption
from common import demandimport
demandimport.enable()
demandimport.ignore += ['_imp', '_thread', 'axolotl', 'PIL',
@@ -832,6 +833,7 @@ class OmemoPlugin(GajimPlugin):
chat_control : ChatControl
Gajim ChatControl object
"""
+ FileDecryption(self).activate(chat_control)
account = chat_control.contact.account.name
if account in self.disabled_accounts:
return
diff --git a/omemo/upload_progress_dialog.ui b/omemo/upload_progress_dialog.ui
new file mode 100644
index 0000000..c5e0b5a
--- /dev/null
+++ b/omemo/upload_progress_dialog.ui
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk+" version="2.16"/>
+ <!-- interface-naming-policy toplevel-contextual -->
+ <object class="GtkDialog" id="progress_dialog">
+ <property name="can_focus">True</property>
+ <property name="title" translatable="yes">Download</property>
+ <property name="resizable">False</property>
+ <property name="window_position">center-on-parent</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="icon_name">go-down</property>
+ <property name="type_hint">dialog</property>
+ <child internal-child="vbox">
+ <object class="GtkVBox" id="dialog-vbox">
+ <property name="width_request">250</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">6</property>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="dialog-action_area11">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkAlignment" id="alignment3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">2</property>
+ <property name="bottom_padding">4</property>
+ <property name="right_padding">3</property>
+ <child>
+ <object class="GtkButton" id="close_button">
+ <property name="label">gtk-cancel</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_stock">True</property>
+ <signal name="destroy" handler="on_destroy" swapped="no"/>
+ <signal name="clicked" handler="close_dialog" swapped="no"/>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">8</property>
+ <property name="bottom_padding">4</property>
+ <property name="left_padding">8</property>
+ <property name="right_padding">8</property>
+ <child>
+ <object class="GtkLabel" id="label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">4</property>
+ <property name="bottom_padding">4</property>
+ <property name="left_padding">8</property>
+ <property name="right_padding">8</property>
+ <child>
+ <object class="GtkProgressBar" id="progressbar">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="show_text">True</property>
+ <property name="pulse_step">0.10000000149</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>