Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
Malte L
gajim-plugins
Commits
75052806
Commit
75052806
authored
Mar 26, 2017
by
Philipp Hörist
Browse files
[omemo] Add file decryption
parent
65e2f437
Changes
3
Hide whitespace changes
Inline
Side-by-side
omemo/file_decryption.py
0 → 100644
View file @
75052806
# -*- 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
socket
import
threading
import
platform
import
subprocess
import
binascii
from
urllib.request
import
urlopen
from
urllib.error
import
URLError
from
urllib.parse
import
urlparse
,
urldefrag
from
io
import
BufferedWriter
,
FileIO
,
BytesIO
from
gi.repository
import
GLib
import
gtkgui_helpers
from
common
import
configpaths
from
dialogs
import
ErrorDialog
,
YesNoDialog
if
os
.
name
==
'nt'
:
import
certifi
log
=
logging
.
getLogger
(
'gajim.plugin_system.omemo.filedecryption'
)
ERROR
=
False
try
:
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
except
ImportError
:
log
.
exception
(
'ImportError'
)
ERROR
=
True
DIRECTORY
=
os
.
path
.
join
(
configpaths
.
gajimpaths
[
'MY_DATA'
],
'downloads'
)
try
:
if
not
os
.
path
.
exists
(
DIRECTORY
):
os
.
makedirs
(
DIRECTORY
)
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
.
window
=
None
def
hyperlink_handler
(
self
,
url
,
kind
,
instance
,
window
):
if
ERROR
or
kind
!=
'url'
:
return
self
.
window
=
window
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
)
return
if
not
self
.
is_encrypted
(
file
):
log
.
info
(
'Url not encrypted: %s'
,
url
)
return
self
.
create_paths
(
file
)
if
os
.
path
.
exists
(
file
.
filepath
):
instance
.
plugin_modified
=
True
self
.
finished
(
file
)
return
event
=
threading
.
Event
()
progressbar
=
ProgressWindow
(
self
.
plugin
,
self
.
window
,
event
)
thread
=
threading
.
Thread
(
target
=
Download
,
args
=
(
file
,
progressbar
,
self
.
window
,
event
,
self
))
thread
.
daemon
=
True
thread
.
start
()
instance
.
plugin_modified
=
True
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
.
encode
(
'utf-8'
)).
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
.
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
,
window
,
event
,
base
):
self
.
file
=
file
self
.
progressbar
=
progressbar
self
.
window
=
window
self
.
event
=
event
self
.
base
=
base
self
.
download
()
def
download
(
self
):
GLib
.
idle_add
(
self
.
progressbar
.
set_text
,
'Downloading...'
)
data
=
self
.
load_url
()
if
isinstance
(
data
,
str
):
GLib
.
idle_add
(
self
.
progressbar
.
close_dialog
)
GLib
.
idle_add
(
self
.
error
,
data
)
return
GLib
.
idle_add
(
self
.
progressbar
.
set_text
,
'Decrypting...'
)
decrypted_data
=
self
.
aes_decrypt
(
data
)
GLib
.
idle_add
(
self
.
progressbar
.
set_text
,
'Writing file to harddisk...'
)
self
.
write_file
(
decrypted_data
)
GLib
.
idle_add
(
self
.
progressbar
.
close_dialog
)
GLib
.
idle_add
(
self
.
base
.
finished
,
self
.
file
)
def
load_url
(
self
):
try
:
stream
=
BytesIO
()
if
os
.
name
==
'nt'
:
get_request
=
urlopen
(
self
.
file
.
url
,
cafile
=
certifi
.
where
(),
timeout
=
30
)
else
:
get_request
=
urlopen
(
self
.
file
.
url
,
timeout
=
30
)
size
=
get_request
.
info
()[
'Content-Length'
]
if
not
size
:
errormsg
=
'Content-Length not found in header'
log
.
error
(
errormsg
)
return
errormsg
while
True
:
try
:
if
self
.
event
.
isSet
():
raise
UploadAbortedException
temp
=
get_request
.
read
(
10000
)
GLib
.
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
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
.
window
)
return
False
class
ProgressWindow
:
def
__init__
(
self
,
plugin
,
window
,
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
(
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'
)
omemo/omemoplugin.py
View file @
75052806
...
...
@@ -30,6 +30,7 @@ from plugins import GajimPlugin
from
plugins.helpers
import
log_calls
from
nbxmpp.simplexml
import
Node
from
nbxmpp
import
NS_CORRECT
,
NS_ADDRESS
from
.file_decryption
import
FileDecryption
from
.xmpp
import
(
NS_NOTIFY
,
NS_OMEMO
,
NS_EME
,
BundleInformationAnnouncement
,
...
...
@@ -115,7 +116,9 @@ class OmemoPlugin(GajimPlugin):
self
.
gui_extension_points
=
{
'chat_control'
:
(
self
.
connect_ui
,
self
.
disconnect_ui
),
'groupchat_control'
:
(
self
.
connect_ui
,
self
.
disconnect_ui
)}
self
.
disconnect_ui
),
'hyperlink_handler'
:
(
self
.
file_decryption
,
None
)}
SUPPORTED_PERSONAL_USER_EVENTS
.
append
(
DevicelistPEP
)
self
.
plugin
=
self
self
.
announced
=
[]
...
...
@@ -179,6 +182,9 @@ class OmemoPlugin(GajimPlugin):
'enable_esessions'
,
False
)
log
.
info
(
str
(
account
)
+
" => Gajim E2E encryption disabled"
)
def
file_decryption
(
self
,
url
,
kind
,
instance
,
window
):
FileDecryption
(
self
).
hyperlink_handler
(
url
,
kind
,
instance
,
window
)
@
log_calls
(
'OmemoPlugin'
)
def
signed_in
(
self
,
event
):
""" Method called on SignIn
...
...
omemo/upload_progress_dialog.ui
0 → 100644
View file @
75052806
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.0 -->
<interface>
<requires
lib=
"gtk+"
version=
"3.0"
/>
<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=
"GtkBox"
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=
"GtkButtonBox"
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=
"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=
"clicked"
handler=
"close_dialog"
swapped=
"no"
/>
<signal
name=
"destroy"
handler=
"on_destroy"
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=
"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=
"pulse_step"
>
0.10000000149
</property>
<property
name=
"show_text"
>
True
</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>
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment