70cdbd972dfd0f02a64cc882233ccac6769d5b75
[conversations_http_downloader.git] / conversations_http_downloader.py
1 #!/usr/bin/env python3
2 #
3 # conversations_http_downloader - download files uploaded by Conversations
4 #
5 # Copyright (C) 2016  Antonio Ospite <ao2@ao2.it>
6 #
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
20 import os
21 import sys
22 import urllib.error
23 import urllib.parse
24 import urllib.request
25
26 from cryptography.hazmat.backends import default_backend
27 from cryptography.hazmat.primitives.ciphers import algorithms
28 from cryptography.hazmat.primitives.ciphers import Cipher
29 from cryptography.hazmat.primitives.ciphers.modes import GCM
30
31
32 # Taken verbatim from python-omemo
33 def aes_decrypt(key, iv, payload):
34     """ Use AES128 GCM with the given key and iv to decrypt the payload. """
35     data = payload[:-16]
36     tag = payload[-16:]
37     backend = default_backend()
38     decryptor = Cipher(
39         algorithms.AES(key),
40         GCM(iv, tag=tag),
41         backend=backend).decryptor()
42     return decryptor.update(data) + decryptor.finalize()
43
44
45 # Inspired by https://github.com/iNPUTmice/ImageDownloader
46 def decrypt(data, anchor):
47     """ Use the iv an key that Conversations put into the URL anchor. """
48     key_and_iv = bytes.fromhex(anchor)
49     iv = key_and_iv[0:16]
50     key = key_and_iv[16:48]
51     return aes_decrypt(key, iv, data)
52
53
54 def usage(name):
55     print("usage: %s <URL>" % name)
56
57
58 def main():
59     if len(sys.argv) != 2:
60         usage(sys.argv[0])
61         return 1
62
63     url = sys.argv[1]
64
65     parsed_url = urllib.parse.urlparse(url)
66     anchor = parsed_url.fragment
67
68     try:
69         response = urllib.request.urlopen(url)
70         data = response.read()
71     except (urllib.error.URLError, urllib.error.HTTPError) as error:
72         sys.stderr.write("Error: %s\n" % error)
73         return 1
74
75     filename = os.path.basename(parsed_url.path)
76     with open(filename, "xb") as output_file:
77         if len(anchor) == 96:
78             output_file.write(decrypt(data, anchor))
79         else:
80             output_file.write(data)
81
82     return 0
83
84
85 if __name__ == "__main__":
86     sys.exit(main())