Initial import
authorAntonio Ospite <ao2@ao2.it>
Fri, 10 Jun 2016 13:31:57 +0000 (15:31 +0200)
committerAntonio Ospite <ao2@ao2.it>
Fri, 10 Jun 2016 14:42:24 +0000 (16:42 +0200)
README [new file with mode: 0644]
conversations_http_downloader.py [new file with mode: 0755]
open_wrapper.sh [new file with mode: 0755]

diff --git a/README b/README
new file mode 100644 (file)
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 (executable)
index 0000000..70cdbd9
--- /dev/null
@@ -0,0 +1,86 @@
+#!/usr/bin/env python3
+#
+# conversations_http_downloader - download files uploaded by Conversations
+#
+# Copyright (C) 2016  Antonio Ospite <ao2@ao2.it>
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+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 <URL>" % 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 (executable)
index 0000000..6a3d0d3
--- /dev/null
@@ -0,0 +1,42 @@
+#!/bin/sh
+#
+# open_warapper.sh - wrapper script for conversations_http_downloader.py
+#
+# Copyright (C) 2016  Antonio Ospite <ao2@ao2.it>
+#
+# 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"