chiark / gitweb /
c5b115b8eac773f535becbc47e2bb4ae30857cfe
[fdroidserver.git] / fdroidserver / signatures.py
1 #!/usr/bin/env python3
2 #
3 # Copyright (C) 2017, Michael Poehn <michael.poehn@fsfe.org>
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU Affero General Public License for more details.
14 #
15 # You should have received a copy of the GNU Affero General Public License
16 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
17
18 from argparse import ArgumentParser
19
20 import re
21 import os
22 import sys
23 import logging
24
25 from . import _
26 from . import common
27 from . import net
28 from .exception import FDroidException
29
30
31 def extract_signature(apkpath):
32
33     if not os.path.exists(apkpath):
34         raise FDroidException("file APK does not exists '{}'".format(apkpath))
35     if not common.verify_apk_signature(apkpath):
36         raise FDroidException("no valid signature in '{}'".format(apkpath))
37     logging.debug('signature okay: %s', apkpath)
38
39     appid, vercode, _ignored = common.get_apk_id_aapt(apkpath)
40     sigdir = common.metadata_get_sigdir(appid, vercode)
41     if not os.path.exists(sigdir):
42         os.makedirs(sigdir)
43     common.apk_extract_signatures(apkpath, sigdir)
44
45     return sigdir
46
47
48 def extract(config, options):
49
50     # Create tmp dir if missing…
51     tmp_dir = 'tmp'
52     if not os.path.exists(tmp_dir):
53         os.mkdir(tmp_dir)
54
55     if not options.APK or len(options.APK) <= 0:
56         logging.critical(_('no APK supplied'))
57         sys.exit(1)
58
59     # iterate over supplied APKs downlaod and extract them…
60     httpre = re.compile('https?:\/\/')
61     for apk in options.APK:
62         try:
63             if os.path.isfile(apk):
64                 sigdir = extract_signature(apk)
65                 logging.info(_("Fetched signatures for '{apkfilename}' -> '{sigdir}'")
66                              .format(apkfilename=apk, sigdir=sigdir))
67             elif httpre.match(apk):
68                 if apk.startswith('https') or options.no_check_https:
69                     try:
70                         tmp_apk = os.path.join(tmp_dir, 'signed.apk')
71                         net.download_file(apk, tmp_apk)
72                         sigdir = extract_signature(tmp_apk)
73                         logging.info(_("Fetched signatures for '{apkfilename}' -> '{sigdir}'")
74                                      .format(apkfilename=apk, sigdir=sigdir))
75                     finally:
76                         if tmp_apk and os.path.exists(tmp_apk):
77                             os.remove(tmp_apk)
78                 else:
79                     logging.warn(_('refuse downloading via insecure HTTP connection (use HTTPS or specify --no-https-check): {apkfilename}').format(apkfilename=apk))
80         except FDroidException as e:
81             logging.warning(_("Failed fetching signatures for '{apkfilename}': {error}")
82                             .format(apkfilename=apk, error=e))
83             if e.detail:
84                 logging.debug(e.detail)
85
86
87 def main():
88
89     global config, options
90
91     # Parse command line...
92     parser = ArgumentParser(usage="%(prog)s [options] APK [APK...]")
93     common.setup_global_opts(parser)
94     parser.add_argument("APK", nargs='*',
95                         help=_("signed APK, either a file-path or HTTPS URL."))
96     parser.add_argument("--no-check-https", action="store_true", default=False)
97     options = parser.parse_args()
98
99     # Read config.py...
100     config = common.read_config(options)
101
102     extract(config, options)