From bc6c68a6ef93db27ee08b331e08790ade0d040e4 Mon Sep 17 00:00:00 2001 From: Antonio Ospite Date: Fri, 10 Jun 2016 15:31:57 +0200 Subject: [PATCH 1/1] Initial import --- README | 25 ++++++++++++ conversations_http_downloader.py | 86 ++++++++++++++++++++++++++++++++++++++++ open_wrapper.sh | 42 ++++++++++++++++++++ 3 files changed, 153 insertions(+) create mode 100644 README create mode 100755 conversations_http_downloader.py create mode 100755 open_wrapper.sh diff --git a/README b/README new file mode 100644 index 0000000..1b602f7 --- /dev/null +++ b/README @@ -0,0 +1,25 @@ +Download and decrypt encrypted files uploaded by the Conversations XMPP client +using the mechanism from XEP-0363: HTTP Upload: +(http://xmpp.org/extensions/xep-0363.html) + +This is basically a translation in python of ImageDownlaoder: +https://github.com/iNPUTmice/ImageDownloader + +However there are some differences: + - The file is saved on local storage instead of being written on the + standard output, this way xdg-open can be used to open it. + - The wrapper script uses xdg-open, this is a more generic approach which + works with different file types. + - The wrapper script downloads the file to a temporary directory, this way + it can be invoked multiple times without the risk of overwriting existing + files. + +Some snippets were taken from python-omemo: +https://github.com/omemo/python-omemo/blob/HEAD/src/omemo/aes_gcm_native.py + +Example of use: + + ./conversations_http_downloader.py http://host.tld/path/to/file.jpg#theivandkey + +The open_wrapper.sh script can be used in gajim as a browser command to +download, decrypt, and open the files automatically. diff --git a/conversations_http_downloader.py b/conversations_http_downloader.py new file mode 100755 index 0000000..70cdbd9 --- /dev/null +++ b/conversations_http_downloader.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +# +# conversations_http_downloader - download files uploaded by Conversations +# +# Copyright (C) 2016 Antonio Ospite +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os +import sys +import urllib.error +import urllib.parse +import urllib.request + +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives.ciphers import algorithms +from cryptography.hazmat.primitives.ciphers import Cipher +from cryptography.hazmat.primitives.ciphers.modes import GCM + + +# Taken verbatim from python-omemo +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:] + backend = default_backend() + decryptor = Cipher( + algorithms.AES(key), + GCM(iv, tag=tag), + backend=backend).decryptor() + return decryptor.update(data) + decryptor.finalize() + + +# Inspired by https://github.com/iNPUTmice/ImageDownloader +def decrypt(data, anchor): + """ Use the iv an key that Conversations put into the URL anchor. """ + key_and_iv = bytes.fromhex(anchor) + iv = key_and_iv[0:16] + key = key_and_iv[16:48] + return aes_decrypt(key, iv, data) + + +def usage(name): + print("usage: %s " % name) + + +def main(): + if len(sys.argv) != 2: + usage(sys.argv[0]) + return 1 + + url = sys.argv[1] + + parsed_url = urllib.parse.urlparse(url) + anchor = parsed_url.fragment + + try: + response = urllib.request.urlopen(url) + data = response.read() + except (urllib.error.URLError, urllib.error.HTTPError) as error: + sys.stderr.write("Error: %s\n" % error) + return 1 + + filename = os.path.basename(parsed_url.path) + with open(filename, "xb") as output_file: + if len(anchor) == 96: + output_file.write(decrypt(data, anchor)) + else: + output_file.write(data) + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/open_wrapper.sh b/open_wrapper.sh new file mode 100755 index 0000000..6a3d0d3 --- /dev/null +++ b/open_wrapper.sh @@ -0,0 +1,42 @@ +#!/bin/sh +# +# open_warapper.sh - wrapper script for conversations_http_downloader.py +# +# Copyright (C) 2016 Antonio Ospite +# +# This program is free software. It comes without any warranty, to +# the extent permitted by applicable law. You can redistribute it +# and/or modify it under the terms of the Do What The Fuck You Want +# To Public License, Version 2, as published by Sam Hocevar. See +# http://sam.zoy.org/wtfpl/COPYING for more details. +# +# This script is based on +# https://github.com/iNPUTmice/ImageDownloader/blob/master/openbrowser.sh + +set -e + +DOWNLOADER="/home/ao2/Proj/conversations_http_downloader/conversations_http_downloader.py" +URL="$1" + +decrypted_file() +{ + URL="$1" + + TEMPDIR=$(mktemp -d) + + cd $TEMPDIR + $DOWNLOADER "$URL" + cd $OLDPWD + + FILENAME=$(basename ${URL%#*}) + echo "${TEMPDIR}/${FILENAME}" +} + +if [ ${URL: -97:1} = "#" ]; +then + DESTINATION=$(decrypted_file "$URL") +else + DESTINATION="$URL" +fi + +exec xdg-open "$DESTINATION" -- 2.1.4