From be2926ffc88ddc8b72d27f29086869da79848248 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Tue, 27 Jun 2017 23:33:24 +0200 Subject: [PATCH 01/10] tests: test moving files to and from the archive #166 --- tests/metadata/com.politedroid.txt | 36 +++++++++ tests/repo/categories.txt | 1 + tests/repo/com.politedroid_3.apk | Bin 0 -> 17552 bytes tests/repo/com.politedroid_4.apk | Bin 0 -> 18489 bytes tests/repo/com.politedroid_5.apk | Bin 0 -> 18817 bytes tests/repo/com.politedroid_6.apk | Bin 0 -> 16578 bytes tests/repo/index.xml | 65 ++++++++++++++++ tests/run-tests | 114 ++++++++++++++++++++++++++++- tests/stats/known_apks.txt | 4 + tests/update.TestCase | 10 ++- 10 files changed, 228 insertions(+), 2 deletions(-) create mode 100644 tests/metadata/com.politedroid.txt create mode 100644 tests/repo/com.politedroid_3.apk create mode 100644 tests/repo/com.politedroid_4.apk create mode 100644 tests/repo/com.politedroid_5.apk create mode 100644 tests/repo/com.politedroid_6.apk diff --git a/tests/metadata/com.politedroid.txt b/tests/metadata/com.politedroid.txt new file mode 100644 index 00000000..526be787 --- /dev/null +++ b/tests/metadata/com.politedroid.txt @@ -0,0 +1,36 @@ +Categories:Time +License:GPL-3.0 +Web Site: +Source Code:https://github.com/miguelvps/PoliteDroid +Issue Tracker:https://github.com/miguelvps/PoliteDroid/issues + +Auto Name:Polite Droid +Summary:Calendar tool +Description: +Activates silent mode during calendar events. +. + +Repo Type:git +Repo:https://github.com/miguelvps/PoliteDroid.git + +Build:1.2,3 + commit=6a548e4b19 + target=android-10 + +Build:1.3,4 + commit=ad865b57bf3ac59580f38485608a9b1dda4fa7dc + target=android-15 + +Build:1.4,5 + commit=456bd615f3fbe6dff06433928cf7ea20073601fb + target=android-10 + +Build:1.5,6 + commit=v1.5 + gradle=yes + +Archive Policy:4 versions +Auto Update Mode:Version v%v +Update Check Mode:Tags +Current Version:1.5 +Current Version Code:6 diff --git a/tests/repo/categories.txt b/tests/repo/categories.txt index a4664e81..d4a50083 100644 --- a/tests/repo/categories.txt +++ b/tests/repo/categories.txt @@ -7,3 +7,4 @@ None Phone & SMS Security System +Time diff --git a/tests/repo/com.politedroid_3.apk b/tests/repo/com.politedroid_3.apk new file mode 100644 index 0000000000000000000000000000000000000000..19634ba9d09b7bd1af0a91b233cf93268ada5fc9 GIT binary patch literal 17552 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 aZ;9lkU?E#7fqjs7K+C|k7`|_t2LC@$7GV4U literal 0 HcmV?d00001 diff --git a/tests/repo/com.politedroid_5.apk b/tests/repo/com.politedroid_5.apk new file mode 100644 index 0000000000000000000000000000000000000000..35e0ed6c39219dcda54c0934576025fc78a15cba GIT binary patch literal 18817 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 literal 0 HcmV?d00001 diff --git a/tests/repo/com.politedroid_6.apk b/tests/repo/com.politedroid_6.apk new file mode 100644 index 0000000000000000000000000000000000000000..f48d80824b9ea53866217e058e718d76d4b6f3d9 GIT binary patch literal 16578 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 + + com.politedroid + 2017-06-23 + 2017-06-23 + Polite Droid + Calendar tool + com.politedroid.6.png + <p>Activates silent mode during calendar events.</p> + GPL-3.0 + Time + Time + + https://github.com/miguelvps/PoliteDroid + https://github.com/miguelvps/PoliteDroid/issues + 1.5 + 6 + + 1.5 + 6 + com.politedroid_6.apk + 70c2f776a2bac38a58a7d521f96ee0414c6f0fb1de973c3ca8b10862a009247d + 16578 + 14 + 21 + 2017-06-23 + b4964fd759edaa54e65bb476d0276880 + READ_CALENDAR,RECEIVE_BOOT_COMPLETED + + + 1.4 + 5 + com.politedroid_5.apk + 5bdbfa071cca4b8d05ced41d6b28763595d6e8096cca5bbf0f9253c9a2622e5d + 18817 + 3 + 10 + 2017-06-23 + b4964fd759edaa54e65bb476d0276880 + READ_CALENDAR,RECEIVE_BOOT_COMPLETED + + + 1.3 + 4 + com.politedroid_4.apk + c809bdff83715fbf919f3840ee09869b038e209378b906e135ee40d3f0e1f075 + 18489 + 3 + 3 + 2017-06-23 + b4964fd759edaa54e65bb476d0276880 + READ_CALENDAR,READ_EXTERNAL_STORAGE,READ_PHONE_STATE,RECEIVE_BOOT_COMPLETED,WRITE_EXTERNAL_STORAGE + + + 1.2 + 3 + com.politedroid_3.apk + 665d03d61ebc642289fda697f71a59305b0202b16cafc5ffdae91cbe91f0b25d + 17552 + 3 + 3 + 2017-06-23 + b4964fd759edaa54e65bb476d0276880 + READ_CALENDAR,READ_EXTERNAL_STORAGE,READ_PHONE_STATE,RECEIVE_BOOT_COMPLETED,WRITE_EXTERNAL_STORAGE + + info.guardianproject.urzip 2016-06-23 diff --git a/tests/run-tests b/tests/run-tests index a3d62d27..6afaf5cd 100755 --- a/tests/run-tests +++ b/tests/run-tests @@ -237,7 +237,119 @@ test -e repo/obb.main.twoversions_1101617_src.tar.gz.asc # we can't easily reproduce the timestamps for things, so just hardcode them sed -i --expression='s,timestamp="[0-9]*",timestamp="1480431575",' repo/index.xml -diff $WORKSPACE/tests/repo/index.xml repo/index.xml +diff -uw $WORKSPACE/tests/repo/index.xml repo/index.xml + + +#------------------------------------------------------------------------------# +echo_header 'test per-app "Archive Policy"' + +REPOROOT=`create_test_dir` +cd $REPOROOT +cp $WORKSPACE/tests/keystore.jks $REPOROOT/ +$fdroid init --keystore keystore.jks --repo-keyalias=sova +echo 'keystorepass = "r9aquRHYoI8+dYz6jKrLntQ5/NJNASFBacJh7Jv2BlI="' >> config.py +echo 'keypass = "r9aquRHYoI8+dYz6jKrLntQ5/NJNASFBacJh7Jv2BlI="' >> config.py +echo "accepted_formats = ['txt']" >> config.py +test -d metadata || mkdir metadata +cp $WORKSPACE/tests/metadata/com.politedroid.txt metadata/ +test -d repo || mkdir repo +cp $WORKSPACE/tests/repo/com.politedroid_[0-9].apk repo/ +sed -i 's,archive_older = [0-9],archive_older = 3,' config.py + +$fdroid update --pretty --nosign +test `grep '' archive/index.xml | wc -l` -eq 0 +test `grep '' repo/index.xml | wc -l` -eq 4 +grep -F com.politedroid_3.apk repo/index.xml +grep -F com.politedroid_4.apk repo/index.xml +grep -F com.politedroid_5.apk repo/index.xml +grep -F com.politedroid_6.apk repo/index.xml +test -e repo/com.politedroid_3.apk +test -e repo/com.politedroid_4.apk +test -e repo/com.politedroid_5.apk +test -e repo/com.politedroid_6.apk + +echo "enable one app in the repo" +sed -i 's,^Archive Policy:4,Archive Policy:1,' metadata/com.politedroid.txt +$fdroid update --pretty --nosign +test `grep '' archive/index.xml | wc -l` -eq 3 +test `grep '' repo/index.xml | wc -l` -eq 1 +grep -F com.politedroid_3.apk archive/index.xml +grep -F com.politedroid_4.apk archive/index.xml +grep -F com.politedroid_5.apk archive/index.xml +grep -F com.politedroid_6.apk repo/index.xml +test -e archive/com.politedroid_3.apk +test -e archive/com.politedroid_4.apk +test -e archive/com.politedroid_5.apk +test -e repo/com.politedroid_6.apk + + +#------------------------------------------------------------------------------# +echo_header 'test moving old APKs to and from the archive' + +REPOROOT=`create_test_dir` +cd $REPOROOT +cp $WORKSPACE/tests/keystore.jks $REPOROOT/ +$fdroid init --keystore keystore.jks --repo-keyalias=sova +echo 'keystorepass = "r9aquRHYoI8+dYz6jKrLntQ5/NJNASFBacJh7Jv2BlI="' >> config.py +echo 'keypass = "r9aquRHYoI8+dYz6jKrLntQ5/NJNASFBacJh7Jv2BlI="' >> config.py +echo "accepted_formats = ['txt']" >> config.py +test -d metadata || mkdir metadata +cp $WORKSPACE/tests/metadata/com.politedroid.txt metadata/ +sed -i '/Archive Policy:/d' metadata/com.politedroid.txt +test -d repo || mkdir repo +cp $WORKSPACE/tests/repo/com.politedroid_[0-9].apk repo/ +sed -i 's,archive_older = [0-9],archive_older = 3,' config.py + +$fdroid update --pretty --nosign +test `grep '' archive/index.xml | wc -l` -eq 1 +test `grep '' repo/index.xml | wc -l` -eq 3 +grep -F com.politedroid_3.apk archive/index.xml +grep -F com.politedroid_4.apk repo/index.xml +grep -F com.politedroid_5.apk repo/index.xml +grep -F com.politedroid_6.apk repo/index.xml +test -e archive/com.politedroid_3.apk +test -e repo/com.politedroid_4.apk +test -e repo/com.politedroid_5.apk +test -e repo/com.politedroid_6.apk + +sed -i 's,archive_older = 3,archive_older = 1,' config.py +$fdroid update --pretty --nosign +test `grep '' archive/index.xml | wc -l` -eq 3 +test `grep '' repo/index.xml | wc -l` -eq 1 +grep -F com.politedroid_3.apk archive/index.xml +grep -F com.politedroid_4.apk archive/index.xml +grep -F com.politedroid_5.apk archive/index.xml +grep -F com.politedroid_6.apk repo/index.xml +test -e archive/com.politedroid_3.apk +test -e archive/com.politedroid_4.apk +test -e archive/com.politedroid_5.apk +test -e repo/com.politedroid_6.apk + +# disabling deletes from the archive +sed -i 's/Build:1.3,4/Build:1.3,4\n disable=testing deletion/' metadata/com.politedroid.txt +$fdroid update --pretty --nosign +test `grep '' archive/index.xml | wc -l` -eq 2 +test `grep '' repo/index.xml | wc -l` -eq 1 +grep -F com.politedroid_3.apk archive/index.xml +! grep -F com.politedroid_4.apk archive/index.xml +grep -F com.politedroid_5.apk archive/index.xml +grep -F com.politedroid_6.apk repo/index.xml +test -e archive/com.politedroid_3.apk +! test -e archive/com.politedroid_4.apk +test -e archive/com.politedroid_5.apk +test -e repo/com.politedroid_6.apk + +# disabling deletes from the repo, and promotes one from the archive +sed -i 's/Build:1.5,6/Build:1.5,6\n disable=testing deletion/' metadata/com.politedroid.txt +$fdroid update --pretty --nosign +test `grep '' archive/index.xml | wc -l` -eq 1 +test `grep '' repo/index.xml | wc -l` -eq 1 +grep -F com.politedroid_3.apk archive/index.xml +grep -F com.politedroid_5.apk repo/index.xml +! grep -F com.politedroid_6.apk repo/index.xml +test -e archive/com.politedroid_3.apk +test -e repo/com.politedroid_5.apk +! test -e repo/com.politedroid_6.apk #------------------------------------------------------------------------------# diff --git a/tests/stats/known_apks.txt b/tests/stats/known_apks.txt index 329213b7..6d457a6f 100644 --- a/tests/stats/known_apks.txt +++ b/tests/stats/known_apks.txt @@ -1,3 +1,7 @@ +com.politedroid_3.apk repo/com.politedroid 2017-06-23 +com.politedroid_4.apk repo/com.politedroid 2017-06-23 +com.politedroid_5.apk repo/com.politedroid 2017-06-23 +com.politedroid_6.apk repo/com.politedroid 2017-06-23 fake.ota.update_1234.zip fake.ota.update 2016-03-10 obb.main.oldversion_1444412523.apk obb.main.oldversion 2013-12-31 obb.main.twoversions_1101613.apk obb.main.twoversions 2015-10-12 diff --git a/tests/update.TestCase b/tests/update.TestCase index 7e1626c5..cd47e020 100755 --- a/tests/update.TestCase +++ b/tests/update.TestCase @@ -205,8 +205,16 @@ class UpdateTest(unittest.TestCase): apps = fdroidserver.metadata.read_metadata(xref=True) knownapks = fdroidserver.common.KnownApks() apks, cachechanged = fdroidserver.update.scan_apks({}, 'repo', knownapks, False) - self.assertEqual(len(apks), 7) + self.assertEqual(len(apks), 11) apk = apks[0] + self.assertEqual(apk['packageName'], 'com.politedroid') + self.assertEqual(apk['versionCode'], 3) + self.assertEqual(apk['minSdkVersion'], '3') + self.assertEqual(apk['targetSdkVersion'], '3') + self.assertFalse('maxSdkVersion' in apk) + apk = apks[4] + self.assertEqual(apk['packageName'], 'obb.main.oldversion') + self.assertEqual(apk['versionCode'], 1444412523) self.assertEqual(apk['minSdkVersion'], '4') self.assertEqual(apk['targetSdkVersion'], '18') self.assertFalse('maxSdkVersion' in apk) From e1492148fad46fa0a4b6183562442fc994514df8 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Fri, 23 Jun 2017 23:55:12 +0200 Subject: [PATCH 02/10] fix "Archive Policy:" field, APKs can move in/out of archive The original logic was checking keepversions against the len() of ALL the APKs in the repo/archive. The correct thing is to check against the number of APKs available for the given packageName/appid. closes #166 --- fdroidserver/update.py | 45 +++++++++++++++++++++--------------------- tests/run-tests | 31 +++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 22 deletions(-) diff --git a/fdroidserver/update.py b/fdroidserver/update.py index 1f322837..064651fb 100644 --- a/fdroidserver/update.py +++ b/fdroidserver/update.py @@ -1421,6 +1421,22 @@ def make_categories_txt(repodir, categories): def archive_old_apks(apps, apks, archapks, repodir, archivedir, defaultkeepversions): + def move_file(from_dir, to_dir, filename, ignore_missing): + from_path = os.path.join(from_dir, filename) + if ignore_missing and not os.path.exists(from_path): + return + to_path = os.path.join(to_dir, filename) + shutil.move(from_path, to_path) + + def filter_apk_list_sorted(apk_list): + res = [] + for apk in apk_list: + if apk['packageName'] == appid: + res.append(apk) + + # Sort the apk list by version code. First is highest/newest. + return sorted(res, key=lambda apk: apk['versionCode'], reverse=True) + for appid, app in apps.items(): if app.ArchivePolicy: @@ -1428,29 +1444,13 @@ def archive_old_apks(apps, apks, archapks, repodir, archivedir, defaultkeepversi else: keepversions = defaultkeepversions - def filter_apk_list_sorted(apk_list): - res = [] - for apk in apk_list: - if apk['packageName'] == appid: - res.append(apk) - - # Sort the apk list by version code. First is highest/newest. - return sorted(res, key=lambda apk: apk['versionCode'], reverse=True) - - def move_file(from_dir, to_dir, filename, ignore_missing): - from_path = os.path.join(from_dir, filename) - if ignore_missing and not os.path.exists(from_path): - return - to_path = os.path.join(to_dir, filename) - shutil.move(from_path, to_path) - logging.debug("Checking archiving for {0} - apks:{1}, keepversions:{2}, archapks:{3}" .format(appid, len(apks), keepversions, len(archapks))) - if len(apks) > keepversions: - apklist = filter_apk_list_sorted(apks) + current_app_apks = filter_apk_list_sorted(apks) + if len(current_app_apks) > keepversions: # Move back the ones we don't want. - for apk in apklist[keepversions:]: + for apk in current_app_apks[keepversions:]: logging.info("Moving " + apk['apkName'] + " to archive") move_file(repodir, archivedir, apk['apkName'], False) move_file(repodir, archivedir, apk['apkName'] + '.asc', True) @@ -1464,11 +1464,12 @@ def archive_old_apks(apps, apks, archapks, repodir, archivedir, defaultkeepversi move_file(repodir, archivedir, apk['srcname'], False) archapks.append(apk) apks.remove(apk) - elif len(apks) < keepversions and len(archapks) > 0: + + current_app_archapks = filter_apk_list_sorted(archapks) + if len(current_app_apks) < keepversions and len(current_app_archapks) > 0: required = keepversions - len(apks) - archapklist = filter_apk_list_sorted(archapks) # Move forward the ones we want again. - for apk in archapklist[:required]: + for apk in current_app_archapks[:required]: logging.info("Moving " + apk['apkName'] + " from archive") move_file(archivedir, repodir, apk['apkName'], False) move_file(archivedir, repodir, apk['apkName'] + '.asc', True) diff --git a/tests/run-tests b/tests/run-tests index 6afaf5cd..5f523ea4 100755 --- a/tests/run-tests +++ b/tests/run-tests @@ -282,6 +282,37 @@ test -e archive/com.politedroid_4.apk test -e archive/com.politedroid_5.apk test -e repo/com.politedroid_6.apk +echo "remove all apps from the repo" +sed -i 's,^Archive Policy:1,Archive Policy:0,' metadata/com.politedroid.txt +$fdroid update --pretty --nosign +test `grep '' archive/index.xml | wc -l` -eq 4 +test `grep '' repo/index.xml | wc -l` -eq 0 +grep -F com.politedroid_3.apk archive/index.xml +grep -F com.politedroid_4.apk archive/index.xml +grep -F com.politedroid_5.apk archive/index.xml +grep -F com.politedroid_6.apk archive/index.xml +test -e archive/com.politedroid_3.apk +test -e archive/com.politedroid_4.apk +test -e archive/com.politedroid_5.apk +test -e archive/com.politedroid_6.apk +! test -e repo/com.politedroid_6.apk + +echo "move back one from archive to the repo" +sed -i 's,^Archive Policy:0,Archive Policy:1,' metadata/com.politedroid.txt +$fdroid update --pretty --nosign +test `grep '' archive/index.xml | wc -l` -eq 3 +test `grep '' repo/index.xml | wc -l` -eq 1 +grep -F com.politedroid_3.apk archive/index.xml +grep -F com.politedroid_4.apk archive/index.xml +grep -F com.politedroid_5.apk archive/index.xml +grep -F com.politedroid_6.apk repo/index.xml +test -e archive/com.politedroid_3.apk +test -e archive/com.politedroid_4.apk +test -e archive/com.politedroid_5.apk +! test -e archive/com.politedroid_6.apk +test -e repo/com.politedroid_6.apk + + #------------------------------------------------------------------------------# echo_header 'test moving old APKs to and from the archive' From 0047f19d5624a5d1b19743549109a72eec20ffcc Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Mon, 26 Jun 2017 21:08:01 +0200 Subject: [PATCH 03/10] update: move duplicated code into move_apk_between_sections() --- fdroidserver/update.py | 54 +++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 29 deletions(-) diff --git a/fdroidserver/update.py b/fdroidserver/update.py index 064651fb..39ab7023 100644 --- a/fdroidserver/update.py +++ b/fdroidserver/update.py @@ -1421,13 +1421,6 @@ def make_categories_txt(repodir, categories): def archive_old_apks(apps, apks, archapks, repodir, archivedir, defaultkeepversions): - def move_file(from_dir, to_dir, filename, ignore_missing): - from_path = os.path.join(from_dir, filename) - if ignore_missing and not os.path.exists(from_path): - return - to_path = os.path.join(to_dir, filename) - shutil.move(from_path, to_path) - def filter_apk_list_sorted(apk_list): res = [] for apk in apk_list: @@ -1451,17 +1444,7 @@ def archive_old_apks(apps, apks, archapks, repodir, archivedir, defaultkeepversi if len(current_app_apks) > keepversions: # Move back the ones we don't want. for apk in current_app_apks[keepversions:]: - logging.info("Moving " + apk['apkName'] + " to archive") - move_file(repodir, archivedir, apk['apkName'], False) - move_file(repodir, archivedir, apk['apkName'] + '.asc', True) - for density in all_screen_densities: - repo_icon_dir = get_icon_dir(repodir, density) - archive_icon_dir = get_icon_dir(archivedir, density) - if density not in apk['icons']: - continue - move_file(repo_icon_dir, archive_icon_dir, apk['icons'][density], True) - if 'srcname' in apk: - move_file(repodir, archivedir, apk['srcname'], False) + move_apk_between_sections(repodir, archivedir, apk) archapks.append(apk) apks.remove(apk) @@ -1470,21 +1453,34 @@ def archive_old_apks(apps, apks, archapks, repodir, archivedir, defaultkeepversi required = keepversions - len(apks) # Move forward the ones we want again. for apk in current_app_archapks[:required]: - logging.info("Moving " + apk['apkName'] + " from archive") - move_file(archivedir, repodir, apk['apkName'], False) - move_file(archivedir, repodir, apk['apkName'] + '.asc', True) - for density in all_screen_densities: - repo_icon_dir = get_icon_dir(repodir, density) - archive_icon_dir = get_icon_dir(archivedir, density) - if density not in apk['icons']: - continue - move_file(archive_icon_dir, repo_icon_dir, apk['icons'][density], True) - if 'srcname' in apk: - move_file(archivedir, repodir, apk['srcname'], False) + move_apk_between_sections(archivedir, repodir, apk) archapks.remove(apk) apks.append(apk) +def move_apk_between_sections(from_dir, to_dir, apk): + """move an APK from repo to archive or vice versa""" + + def _move_file(from_dir, to_dir, filename, ignore_missing): + from_path = os.path.join(from_dir, filename) + if ignore_missing and not os.path.exists(from_path): + return + to_path = os.path.join(to_dir, filename) + shutil.move(from_path, to_path) + + logging.info("Moving %s from %s to %s" % (apk['apkName'], from_dir, to_dir)) + _move_file(from_dir, to_dir, apk['apkName'], False) + _move_file(from_dir, to_dir, apk['apkName'] + '.asc', True) + for density in all_screen_densities: + from_icon_dir = get_icon_dir(from_dir, density) + to_icon_dir = get_icon_dir(to_dir, density) + if density not in apk['icons']: + continue + _move_file(from_icon_dir, to_icon_dir, apk['icons'][density], True) + if 'srcname' in apk: + _move_file(from_dir, to_dir, apk['srcname'], False) + + def add_apks_to_per_app_repos(repodir, apks): apks_per_app = dict() for apk in apks: From b7260ea854aff2621bca9ad67923889313dda827 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Tue, 27 Jun 2017 09:54:35 +0200 Subject: [PATCH 04/10] update: allow deprecated signatures only in the archive In April 2017, Oracle's jarsigner and Google's apksigner both switched to considering any APK signature that uses MD5 as unsigned. Any old build is likely to have a MD5 signature. This sets up the archive as the only place where these "disabled algorithms" are allowed in the repo, and marks any APK signed by a "disabled algorithm" as having a "known vulnerability" This also now automatically moves APKs with invalid signatures to the archive section. #323 --- fdroidserver/common.py | 20 ++++++++++++++++++++ fdroidserver/update.py | 13 +++++++++++-- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/fdroidserver/common.py b/fdroidserver/common.py index acca01dd..884fea4b 100644 --- a/fdroidserver/common.py +++ b/fdroidserver/common.py @@ -2041,6 +2041,26 @@ def verify_apk_signature(apk, jar=False): return subprocess.call([config['jarsigner'], '-strict', '-verify', apk]) == 4 +def verify_old_apk_signature(apk): + """verify the signature on an archived APK, supporting deprecated algorithms + + F-Droid aims to keep every single binary that it ever published. Therefore, + it needs to be able to verify APK signatures that include deprecated/removed + algorithms. For example, jarsigner treats an MD5 signature as unsigned. + + jarsigner passes unsigned APKs as "verified"! So this has to turn + on -strict then check for result 4. + + """ + + _java_security = os.path.join(os.getcwd(), '.java.security') + with open(_java_security, 'w') as fp: + fp.write('jdk.jar.disabledAlgorithms=MD2, RSA keySize < 1024') + + return subprocess.call([config['jarsigner'], '-J-Djava.security.properties=' + _java_security, + '-strict', '-verify', apk]) == 4 + + apk_badchars = re.compile('''[/ :;'"]''') diff --git a/fdroidserver/update.py b/fdroidserver/update.py index 39ab7023..6ef9c126 100644 --- a/fdroidserver/update.py +++ b/fdroidserver/update.py @@ -1184,9 +1184,18 @@ def scan_apk(apkcache, apkfilename, repodir, knownapks, use_date_from_apk): apk['srcname'] = srcfilename apk['size'] = os.path.getsize(apkfile) - # verify the jar signature is correct + # verify the jar signature is correct, allow deprecated + # algorithms only if the APK is in the archive. if not common.verify_apk_signature(apkfile): - return True, None, False + if repodir == 'archive': + if common.verify_old_apk_signature(apkfile): + apk['antiFeatures'].add('KnownVuln') + else: + return True, None, False + else: + logging.warning('Archiving "' + apkfilename + '" with invalid signature!') + move_apk_between_sections('repo', 'archive', apk) + return True, None, False if has_known_vulnerability(apkfile): apk['antiFeatures'].add('KnownVuln') From 746d4bd4cf33cc480e429d459f93c280a4306cf3 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Tue, 27 Jun 2017 21:40:39 +0200 Subject: [PATCH 05/10] update: allow_disabled_algorithms option to keep MD5 sigs in repo The new policy is to move APKs with invalid signatures to the archive, and only add those APKs to the archive's index if they have valid MD5 signatures. closes #323 closes #292 --- examples/config.py | 9 ++ fdroidserver/common.py | 1 + fdroidserver/update.py | 55 +++++++--- ...rg.bitbucket.tickytacky.mirrormirror_1.apk | Bin 0 -> 7173 bytes ...rg.bitbucket.tickytacky.mirrormirror_2.apk | Bin 0 -> 7084 bytes ...rg.bitbucket.tickytacky.mirrormirror_3.apk | Bin 0 -> 7126 bytes ...rg.bitbucket.tickytacky.mirrormirror_4.apk | Bin 0 -> 6588 bytes tests/run-tests | 100 ++++++++++++++++++ tests/update.TestCase | 85 ++++++++++++++- 9 files changed, 233 insertions(+), 17 deletions(-) create mode 100644 tests/org.bitbucket.tickytacky.mirrormirror_1.apk create mode 100644 tests/org.bitbucket.tickytacky.mirrormirror_2.apk create mode 100644 tests/org.bitbucket.tickytacky.mirrormirror_3.apk create mode 100644 tests/org.bitbucket.tickytacky.mirrormirror_4.apk diff --git a/examples/config.py b/examples/config.py index 0a0558b3..974f8ee1 100644 --- a/examples/config.py +++ b/examples/config.py @@ -71,6 +71,15 @@ archive_description = """ The repository of older versions of applications from the main demo repository. """ +# This allows a specific kind of insecure APK to be included in the +# 'repo' section. Since April 2017, APK signatures that use MD5 are +# no longer considered valid, jarsigner and apksigner will return an +# error when verifying. `fdroid update` will move APKs with these +# disabled signatures to the archive. This option stops that +# behavior, and lets those APKs stay part of 'repo'. +# +# allow_disabled_algorithms = True + # Normally, all apps are collected into a single app repository, like on # https://f-droid.org. For certain situations, it is better to make a repo # that is made up of APKs only from a single app. For example, an automated diff --git a/fdroidserver/common.py b/fdroidserver/common.py index 884fea4b..76888cce 100644 --- a/fdroidserver/common.py +++ b/fdroidserver/common.py @@ -85,6 +85,7 @@ default_config = { 'gradle': 'gradle', 'accepted_formats': ['txt', 'yml'], 'sync_from_local_copy_dir': False, + 'allow_disabled_algorithms': False, 'per_app_repos': False, 'make_current_version_link': True, 'current_version_name_source': 'Name', diff --git a/fdroidserver/update.py b/fdroidserver/update.py index 6ef9c126..8488574f 100644 --- a/fdroidserver/update.py +++ b/fdroidserver/update.py @@ -1082,7 +1082,8 @@ def scan_apk_androguard(apk, apkfile): apk['features'].append(feature) -def scan_apk(apkcache, apkfilename, repodir, knownapks, use_date_from_apk): +def scan_apk(apkcache, apkfilename, repodir, knownapks, use_date_from_apk=False, + allow_disabled_algorithms=False, archive_bad_sig=False): """Scan the apk with the given filename in the given repo directory. This also extracts the icons. @@ -1093,6 +1094,9 @@ def scan_apk(apkcache, apkfilename, repodir, knownapks, use_date_from_apk): :param knownapks: known apks info :param use_date_from_apk: use date from APK (instead of current date) for newly added APKs + :param allow_disabled_algorithms: allow APKs with valid signatures that include + disabled algorithms in the signature (e.g. MD5) + :param archive_bad_sig: move APKs with a bad signature to the archive :returns: (skip, apk, cachechanged) where skip is a boolean indicating whether to skip this apk, apk is the scanned apk information, and cachechanged is True if the apkcache got changed. """ @@ -1186,19 +1190,27 @@ def scan_apk(apkcache, apkfilename, repodir, knownapks, use_date_from_apk): # verify the jar signature is correct, allow deprecated # algorithms only if the APK is in the archive. + skipapk = False if not common.verify_apk_signature(apkfile): - if repodir == 'archive': + if repodir == 'archive' or allow_disabled_algorithms: if common.verify_old_apk_signature(apkfile): - apk['antiFeatures'].add('KnownVuln') + apk['antiFeatures'].update(['KnownVuln', 'DisabledAlgorithm']) else: - return True, None, False + skipapk = True else: - logging.warning('Archiving "' + apkfilename + '" with invalid signature!') - move_apk_between_sections('repo', 'archive', apk) - return True, None, False + skipapk = True - if has_known_vulnerability(apkfile): - apk['antiFeatures'].add('KnownVuln') + if skipapk: + if archive_bad_sig: + logging.warning('Archiving "' + apkfilename + '" with invalid signature!') + move_apk_between_sections(repodir, 'archive', apk) + else: + logging.warning('Skipping "' + apkfilename + '" with invalid signature!') + return True, None, False + + if 'KnownVuln' not in apk['antiFeatures']: + if has_known_vulnerability(apkfile): + apk['antiFeatures'].add('KnownVuln') apkzip = zipfile.ZipFile(apkfile, 'r') @@ -1372,7 +1384,9 @@ def scan_apks(apkcache, repodir, knownapks, use_date_from_apk=False): apks = [] for apkfile in sorted(glob.glob(os.path.join(repodir, '*.apk'))): apkfilename = apkfile[len(repodir) + 1:] - (skip, apk, cachechanged) = scan_apk(apkcache, apkfilename, repodir, knownapks, use_date_from_apk) + ada = options.allow_disabled_algorithms or config['allow_disabled_algorithms'] + (skip, apk, cachechanged) = scan_apk(apkcache, apkfilename, repodir, knownapks, + use_date_from_apk, ada, True) if skip: continue apks.append(apk) @@ -1459,12 +1473,16 @@ def archive_old_apks(apps, apks, archapks, repodir, archivedir, defaultkeepversi current_app_archapks = filter_apk_list_sorted(archapks) if len(current_app_apks) < keepversions and len(current_app_archapks) > 0: - required = keepversions - len(apks) - # Move forward the ones we want again. - for apk in current_app_archapks[:required]: - move_apk_between_sections(archivedir, repodir, apk) - archapks.remove(apk) - apks.append(apk) + kept = 0 + # Move forward the ones we want again, except DisableAlgorithm + for apk in current_app_archapks: + if 'DisabledAlgorithm' not in apk['antiFeatures']: + move_apk_between_sections(archivedir, repodir, apk) + archapks.remove(apk) + apks.append(apk) + kept += 1 + if kept == keepversions: + break def move_apk_between_sections(from_dir, to_dir, apk): @@ -1477,6 +1495,9 @@ def move_apk_between_sections(from_dir, to_dir, apk): to_path = os.path.join(to_dir, filename) shutil.move(from_path, to_path) + if from_dir == to_dir: + return + logging.info("Moving %s from %s to %s" % (apk['apkName'], from_dir, to_dir)) _move_file(from_dir, to_dir, apk['apkName'], False) _move_file(from_dir, to_dir, apk['apkName'] + '.asc', True) @@ -1550,6 +1571,8 @@ def main(): help="Use date from apk instead of current time for newly added apks") parser.add_argument("--rename-apks", action="store_true", default=False, help="Rename APK files that do not match package.name_123.apk") + parser.add_argument("--allow-disabled-algorithms", action="store_true", default=False, + help="Include APKs that are signed with disabled algorithms like MD5") metadata.add_metadata_arguments(parser) options = parser.parse_args() metadata.warnings_action = options.W diff --git a/tests/org.bitbucket.tickytacky.mirrormirror_1.apk b/tests/org.bitbucket.tickytacky.mirrormirror_1.apk new file mode 100644 index 0000000000000000000000000000000000000000..6ec4272faeca8391875d168d658b70b4605cde7f GIT binary patch literal 7173 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 Sfr%Wv23*&HbUmCL+53M5B!<=i literal 0 HcmV?d00001 diff --git a/tests/org.bitbucket.tickytacky.mirrormirror_3.apk b/tests/org.bitbucket.tickytacky.mirrormirror_3.apk new file mode 100644 index 0000000000000000000000000000000000000000..af2a1fe839358efc1295aef2b9c897fe5190ca8e GIT binary patch literal 7126 zcmdUUcT^Nh)Ay1EL5UItiIR~dIVd@2kR*8#$p}bZKo9}RNRTW!gG&+w$yqW;PJ&!q za$4f@&FbTo>wBN?ocGVS_S95&b@%VgR893%^{OkOp%a6yUAqPvXn;v?7u*}9M*)Ef z044^3Kq@lY4>%N5Ww}%ys4B?HXlZk*$l4F6I0j1M`!9)LUj|sDV5+mdW1?n$dI7TM zc;{lH>mH+!U|b(gcYZpZr%=4^#k;{@Ua9_D|yHKmMr z-Z!$9fZ7rNgp*W=w4knJp*XCFU0wF{?VXXFg5|E(e*Al1Q#EuvQv_9fuZ8r5G`)OR zR7;Ufk^609iF>lvRK#d%rqcK|=BLQEibem6j0#BV<80frLs1%`m}IZ6z33HBO~t35 zHZb+X&Y8{W@E9z8q$*S>`X@GTe4J=jxRb_=n@X0~IJyv@M90DfVp>Z%!+xA@;BY3y z@!qTc#;BNEgB2>bHo`416oYj*olvYzD2e=9OK=Rd4X-ZQa6ZsbA zz>2c1PtjhXUNZhE0pAmvdK*kIrW|>*h?EAz*It>3e{1WT&)Uksqqp?>C>j#iuIaAS zm_L<;a#ITDO^k4IN}z9`_)fuXV&&i+5>hmZWdEbNeG7 zB_}=uwyyV?L{%~QLPGAW3W$+hV-~d+HCj}6exVHd=5)?c=3?R=wW!?!tGElE8u}(z zrk(RjN(8>MIh$lqdX}$-LK+R$Q;~FI|C;wBVwa{Jt>nr)Y$ab;x3b4~KibGiJWr*P zYT1EY;N_Dx$2J9L#DASXj!`Yt3;v*?uhY;ptEC-{pGAw`Lt@6nOx7_IVY^b8`rd)B zgqI*9#>ZEfJ{A|-8U5ar@oEUn(}|2K^#Upp>4uoXqBkfhnA#N4<7)ALN^C8?T4yP2 z4Z?V0mHs$j9w+wTj)1G7JLw{ptx%gyaXf~2z$c`KuKMAh9%7N#FyKQ6f%q^$Ad;(L z@VAFFwI1+3G3Ph^I`(w`$=N_(_dr!{XR{CF&WXM{c-ZI!CV$uIdBCxRXO6sv}ZW%tT1k@R{XiMSwpnr z!aVd)59}R~Qr6&#C>PMvU5GtSe{MIN`!$2qkNvd6;q2o(}v@n!b( zNaJbRYEFL`j#yR??}vd|qo-6~j7i=ai4R|Df-RJ!P_v_BbWK~n$Y|9ti8&(9!M;(V zM7;E9U(IFEJkj(`n|t;uUsb-6e_Niqodb4gd4KVwKa`^vhqH-IWoYCEFZ0Ii5m;tm ze$WmwyN;)&>%o!o~_8&U)&C-gc_VK5)&7)x~~rcu@WvP2OzW z+@xE#Z-9bEkq&Rj(xJZ52i~K5x!aLZ1p_mq&K`1~;Rs=6sk;C9{Hf;E1Jd}PNBO-!GiCaex6rZaY^um*3k? zd<9H8Mu!w{4cKlnfsX?b2p!~N^^D8T%+t}$mCN4D#(~qr-VV_V0vShtRz%|_4&L5$ zO?{ZJDcv62B-sMyDl;ZAItCGfIhn?oq?lN@e28iCqH=7-3R!M@wJxpr39OW7V*7Fv zjFQ-gs#^uj4L``=;rEfcJg44T67Uc{YzYe&HrOsGGdT!>M5t}oM7#t;jVLMyncg+t zTBlCAvrJ8;^TyqNrzW?fwS97qbzDI9e!a=J&iA{Nxee7gBgGfy1!Z~@p*M=>jzWF* zf;eLg%@Hpli{S*kyFOiVJ6wFS35A$ZXzAK!ev#Y*+nmqxs1e|Sf^ zmT;fMxNP2&SKHgiSV&u4wa7nh^>A?rMG*}I8oGwG6$tde!P3Ri#!|)1!3J#g%oS;? zU>y}TA`N1((LD`CmQ|%p;&y3L3aaa@1h<&*xF4WB<*To$l40_h#6^2a(iyDu^`=wH zbR7=@1o6Lqi{1DH&8S{`uyOxolj@9M+$enB6P|uH?+aInlV9S)5M*8~xn394`ON%q zznx;wC4}3rh?d!$70>Aq_~8nR zD7)#$%074f@Go8)yomDk@CX(w*{M~`bNfcyJ2{?=QbV1bUp3pg&%|~o#6Lm&aqLau zN!MKgO<3oRGs=zazg~xgResOAymd0<@CrdvG)MLnI|ZDhQzg1I7J?%_A0!T=^q9sas$gX zP)qrH+|x)F3w6GVYP}^Qr+K;+>3SPxnTx8#@eHjOE04!AjammS0_PyLIQaWXg3ipm zLtJ%V0pskNShP-XamlXssy4&k2Bd14nscH6{@RP}(KuD#_uTuI9Xg+@9Jfp#IqPrb zWi1FVjPl(yTHU5g=;mBv+H}zJ4H(z@R?xkh=R(3GQ$1rUQeF#*Nm}!Xq4yLx30o-9 zy`a1sEL5X~-Nk}W{!W!)N$^I@o#WMK;2bxXq)ZNp2giN+QT7I%D<+RVilS{q?Ww$- zd2_$Ce^=|R62-tUMWU9Z%TT{Hp9F0p|EaB)6y>QR>aHn4qL!sA)45g7D1=7v|ctmD{7_?e8*R_$@+eWA!BMu=Xcwh~M) zuZ4Tr*#Oq}jj&52k~aYnd|xQxFf!#Zl9F?Ls8}klU7{n~R0j zGfp#?XBI$gqJnUL3zdrK!9E_)jn7e^OPCkJZ~9-hjN&Yhp)2P`TT$|8_xLx7GW$OU8#;slw4Y(TES z)eU3;vISWIZBBq~e)K$nmf3$=oFIEZ>jM0cE3dYHApf7cPyx0EvRaTGAd&V*`a&I$ zNZk(t^z6UdfW!ir5MWAx(b3IZU0r}e@t5&In3zAmO+e$BtBVa_ek7hWF!#Ut78@AD z2N)Ccr_TUT~|NL(a7a{L!Q@;?5& z|6jWQEk4o~NPMLHXDCwHqplbNS|n2eHv9iZ3n)}wRSpk_0*GV~o`SrzCLr%28Tkw# z7n?jJ3Y~)V18w;(3(+=73R82k@q0yP+HD0dHSn@2Wcgn|CDWh)5woGNnr+!KzkANI_8%vrdlugWOB+wX(*?PBMC@pM~J5XQJ@gG`Xo% z`}LWMP(%&<{CRoQ0f%?>SZNb(Y#So5qU) z(qowGJ<4uuS`zCobjoHw(}3_r;kxVM z7=_FlT!NUYK-bs_>zuNx{odUnEae@k9g!XUk~S*EMgE;zb*a)Xrc>nG~n!}y^W=AwkY>_lEsLkp5=`9TBUB{&Y0h9vGD)MmkPF6J;V z8Zj@J4#po|+P}C^!#f3f4`2AvobsA~wi6%3xnMF0SB81|SWUC9kVP%HXnNopKDnOg zJl$)-GU`sPmYlxE{lDJlyEzdY@${Etz$N+ ze)?#sdb)27oBM1P_85Ov%&7B8v{IU^hu_KO$U|_D5vyf>G&_x3jKIcS?Dg*qorm^e zlMN0_67=V{RW|hkKNr9gcJgVyE}&hinew?xT&7&eEnYIdCaQU42p?2| zLT(`z!gr^V_3^~=2Vp6c2e&_@G=Zb)`uU zvb*987QY?)XVTo+@`{`A4cpUqBOH0yONw; z^KO-<4grMxuC#2Q;aVh=JiVC1hz()fZZQ6`x@L(ciyO7jk^p`p7=9U8U|Ug= zCw8-ZeOS;$&UQN`?Od6*D`!PDL5uy1`1rYZ&oy^f-T|Swn!M}brIlx@nzk~ZP%d%9 zMOAms;oB53n1*5@hKF5i?lW{p{d7lVG8@HjH}rH2cD&0A&tZ9cX?c5%c|4T#OgZeP z^nK=YO}New$uX^9OSoLHl77|xcA>ItA$9TMt-hL%YYH<$_By>6I^MHyADzADwut7F z{N7xa7GiS8MnK^O1BpB*?nrE)Sz=m|&H#C}frDHR6wJQun$Gj=NOSy1d8Y4>R|v*m z&qS4d-{#Hita#hAk||L)iSHEY?wt{_LVbH5qs2-H5C=G0S%%%wVa@~Ex0r^;o)*^Ojd zTnLCpdqziOQurcUe4CuDvh1|MB}77PHO1*m`Z^-DeVjT)Wmakjdbo+@uwmgB_dAIY zTV2QSWG#z#fuXFM7&WjDVPk!;M=g22f}h?k zGjV^d)k!up(R9~Ai(6GMzwt6ZIJ~{K@^oBnnoiBj0{y&=oQg2SH9mfLu0etNb`VcD zmtcN22F~kS>hWPY#L1r?Wfo|#CYLodm@C&2cdlQ&jhM{?g%btCitqO1+~FaOW3XyH zV21U2u-f=C}brXg93~}r8K=)TQokpsdn8ae)aUPY_ zX3|LFsGRs}*cB~FtDEAm;wlDyMMBvNLNTMYTP;A zOyVxVF`1(;Lm?8;6thn#gidvQo0SOd4}oS+dDiK6s$1b`+vcfoRG+MN+3I|ubjF=2 zt>}@q-6zOQYP*r~DwH|Yk7Xr^WYkGTvA*-QK6RDxV1?4RiY_ngddBQPnX^4+Mx%|e zOSwM!@yI>@)d~DTv`7p#i524Kq#L-l;}x_ygNYsQnQ=$&QNG|&_TKnJ*0H$EMkSa2 zZiQU6ktVGEakVZN`h{zLG}|rm9VU*%G4gC4`&h4DR`0mFXG>{9otz3b%<+@LhU}X8 zE*}q!n4OieSK7Sa?D*3ugzYg}eNI>~LiAZNYQE~88!RF?8QKfrlzE)trTFGWjGm61Ozap&SqwKKkN~6hkY;38bQ$8IdKjTy80^Jin*P9p0gsVm4nl zMiafqv2Hz&Zpm}0SdXl`hG=(2oZ&XGo>K_6IlvV2zK%l!&NM{53sLlAN14T5fpfMy z7}jqbtH#sfZ%e=ZeZg@ODsw@3!@H>f(q%SCcIYS+_U&wvH#+_>!*EOLCfHZR+qxBv zwiXI!7lK9gHC-^j6NixYoL;<#lkmd^Uo^^9Mq${oBj)o?_Q4COi0jDm>uL(6u7rY0 z3}nUs?Oz*YBKDj9?LVA95w0GEBeU(_WCbLxeT{H-PXC&JZJE##5;Z^8od;QxsL z9H3u?__yP)-w=L4CGs!jAo4KmPmrr+D)LZ!x{*-2Q#u7{5RmSA z8}7ST?)}~0`~R)|nOSSzg`~tpMdTIOBtFR3YmgWz_k zapD_Mz}W9NEBjMNtU_1cWbE4?wtFb#8qn;H`*7CpOU?K^sl;#0%W1wB;!881~<}vvcc~GZL4B(eTSa)J{@KP6w`L>CzZ~0b$MCOz3ZlElM*j z>N3mAl6Pje?Mq(YU$Xf5wOkD`e}V0fe01j|bcM*AC!6ODkd_sAE1Jcsl$a-Px~Czo z>m7}kgH)PPjGL8DD|I8qZzV$L1X#*#~?RY%;zDZSe+)jeci&$$q z<~t_Bg5Tb%iN73(3x=CH3@XLmriId2RaH`k=ah#F&WYdFVcN#;^_id+=Vwg#o)K24 zJ>$Pew{y(i?Tdmc_I7Ayg#-dQ>wFmQN0v;-5Fjt80)#Ggv|g;T(8t9}acSq|j^MU2 z{A^@O;_vnnlZF}AjA7v4i`2EjL2LHl$b%i>8ZpgLuI9S%u{0QIh#(v%s(okHnTaQq zZsN--gy5&p;ao^E3g+YW#tL zC%eEIaA|<+YW}+c4nBTA;zk94$7ldRbba~$WdJ#OAxBH-5n=cN?2JI<)J8Wm+qe5l_azG?k2KcSGVNxJN)B(KRC-hwC_FLR#sm zdK;$_muD>ch03UR%=53fPB;~Uls9!?7k3;;-%~XZaN(nFRd`qS%LQz6%I|Im_TwIT z-atmH^ckB}7kv8=2^RgvGoGg)1>sqllH%!;;#njT&QpL$NlE!Y7A}EK@|fcRtxMd8 z2+vByha@D%p60=(wl=s^z-Z=}d{1j)*%PgfABV*kT{@WeN~ACFSf6N}Y`C0?J85xz zN88UG*qA@FR4=T45Vqp~s(Xe>I)+(AGr+?ke=I8X-iaFD&*}5FMe$lCkW8ToYyFP8C~v8 zXA*r0E99UoI6u;E?+ZA|3LE_V%)v^hI>d5%s$@?I=HeNbUu}C{z^$Y>9eSAPVAhvD zmqg;ua@=fj@@2Q&ox(%nyyk`3>`>0|$mH?9tVzPc$F2Y@{*-pk%AQGe6Uq?{qFD83 zeHU^tJ&6~{X(1mJwd+SjzVC}$St6`S>)_;?eacy!RCekz2YvwVzf-4WTY^ayOHkBf;fhSEIz<#Q8 z*ag0PYHE>p9@j?$ndxG_$sSQ9Q=3E1KzhM6eQtkr95jgzqge8ocCu4VhH5sz^w@i~ z#SpA4aUQM{(0Z47@g1Uqdzvm1(jTY!U~MJk7CMtpNRr}|L^kHYxFxIEgiD$SY#^yzsgIUhYildXXeHknU%;wdTzTMQjJqe^5>9&AuQ6un(-QgK>WMp~G=i9BY$U3dZg4G4 zxaNpCArx6>fC_t9|55ud@u_=tXQxZmj}ekL+;XJZ<3@{IpOs{c`i?O0Q#qVg&^Rqp z8?Nxn=2IoHwdY5P-~6mL7k0vI{yZx1BGa6xsJ=Hi^R=;~=I3VDs1o6eUY5R{1BQ!} zDQ8oYg#K`k^yJE;0Y2u9B`$+PP=tOzbcPJ-y=8DDj;N` zZ*6I;FR5c;3^TB?Wpgw)JMRDh9|cKic_Bjnf`znjxsE77CJa#m$e~%MUtNPYKEdNu z(KJ&DwRs=BIJt+p582eoJ>FoG`k65_N|ywOm{kQ+AL1l zY)r;D$u_|&(cvNWW(;toy(O<9X@Z@m7qkA3B46mIZqXp*XBmoS0d54kFyipFD}4D+hFOX&uQmtZYmmAq5&GsP<%#m#XW&VP z8b+m9M|an;sx;wC2ZQ~Mk;g-A8maFG8y6n%KZ@f~pviilQ`xz!tTcO|o(G4fK`D;q zA}E(in&MlY>JuVtgF{qN&WM%zIszL!p%+^j(9gv-PUMP3o@#t4IlJP@RmHqnCn>|| z(Pna!!}2l>*yf$8DjvN#W*QF^AmWm}u?p1<1V6H%ZUTC$1Y|VE>+c}%af_{ymEsnU z!wX`;K(g_+_CJ_E}`u?c0w5f1PKY#qwG+z0BUZ$YF$Xc$`g+?vb*+=&; z24@#m^gN<12Pa1>j1AaYlAwMwlwY}vOZ@laj=YB_mqJA^@~4ig+&h+EvYh#+Cp}gl zob}Z=8^#|FC6#dR;&iWTg6EQk9wfJ$f9uCSL38FO78n$@99_m$bJnR%Zuv?q(4X2; zU-fCpR-QHABcHbwgTacwgp1?}rb&F^Zg1JAqWPyL+f)$s-24dZ*ZCxSibG{BL5iEG zdt$0wbLKz8xVHKg-5T6IHETg@Z}z$XbD&Yh5#F& z3m5~oU~UKK0j7WfD6@gd_^RXtN;>~5u>s~_tu^>uj=U~kea`>8j1tr*c$Qw>LO_*V z>021&mvvVJwln@IgB%@9x50#ps$*+w4PIvcR6B6vhK;SYF{tk=3?8u0CEgYN!)D;Q z4fg)SLLh<;9Ij=dV{QN-BjW@3U>n%|iZ08SH={qk>yJ7lWFFx4wK_gfzP=s+Ao-7u zU=DhxeHl1_076FUB`y{@xN87hu~))!Ab)t7uI6BEoNz);Z>_1U!x$owl3+= z!)zUe#!tSoxM}2~kRTIMS#p4o8Hq(ZGMy6a4Tyosk8?ip8~W9A81u< zu$P{hj*Sht7xfJsgQH!{2uFzgdb`%vT)uKvbM5QGiS=$9y@3=&+R~Cv@Nke8n$5j2 zW}5q6#9x@G0c&ZIDWerOkz;yrRFLQW;3Iz7F48BaEOMwUolL|lD03gyEyx46Pf#@| zt1LxUN7}B##$?&Z4W-=O?k5NFC%gxVPdT58*xVnlF~&YC*_k|;swke%iPGe;U!PkS zI3Jl7IPI9ed3qk9HX$cWbT^i43a9%b!ZsE8XrFkqNO%xfX&2UkDfuf3Y5L$ipSp>! z5k%njg8o>?dl`4rW>W%={RMjp#i5R-{-w59xDg5|a%Mf+Ch;L|1-4>~zx^9pIAuJF zQk~?c(kA9B-7X6_xhu&ju@Pn}-zd6oJuMtH=&h9uGSNd^DNlBf?U!R$!jvJOkQ!Q`3`)a*^MAbte<7-9Y()&x3_khs<_JUV-X<GMQ(q~K&s{W`gCIFX(UcLk`CR0?mqiU%Ok~;A``kMC%r)4( zNOYs1uNLpj>XtxUuWCxkvlsrZ1X`vs&zR>KwOSWGZk7f0+p`hu(rL-Z@E%rmgnKC* zVm-|2W2=dDS82}QwIWM>;o9x$LK>N;F1k!sQzJWmclc;PU3`~}&0xISAmq3bX8?&U z>~WXD+nZPyOWvn^y&p(v7(CZVXC6uU9ZmTd^G|boP~*0hcJoQqPVz7lIlq+M_aJv4+Vp{b%q@|Mfz=Z9+EwZ=jx zipeROew&8WKYAN#DO!?$C3*Rr+aLPr1g)NvsVr~2@le?9?P;y%v2`eYa>fohK8Y2$ zz^e?en94p{s!T0fUWKi{?;b}?pe+0Bl_>7?8a`p}6D@{B>RE|P7`F9rX$zAO z86LnD(`mI02N;i3iOW_CZzAQ=T4T7KMRn8SH}E{jWjH+%<4HGj(##|nduZ1uh<;bl zdnMd^n73UnfXn!OKPIcM=BzVmdEua-Um38SiS!gVt7Oe!pPm$ga2j&vW5Uwn+ZLt^ z8k9qHdUDZay%;HO26{q|56kY`IvI!MWX<-ELlE>BGwR@hI9k3T= z_0u3X{5y3r-tw9(S7S-UQPG2s0m?(M%Bxn#rb>4b$m?wq5`G=!9Rx+GR%;mZ4XAlsZDqd{L`<=8MwINyRPA z8aPZ|Div48sqv#%-gy{mZ<8QzU*vsE0Sg|8bb7?F$cOXQ+?~`dG7!R%z{jdlDz;8K zxkN#+NRd>urc1UsT$3srTmEhdXM)Ye*DzvzlC4_iL3Jem6!eig^{+EKXYgJ}CsA+Yg4oR2L`@P$?*rJ$1C)@Z#m7}*WGCD>+AuU*R z=7k5zpHN{dx?3~N^h^*BXT!K(B{2sUGU5!}q40Y2+T{mT;4FL#qctR-(aBeS-A^}P zDPI9wNMMy|KHv_@l=ng?uX%XJYdL9E5ldw0EEy_IR4XwFl$5G;|75MJQs7<7Y_0T;`ZhcID)w>rgK<0h zK6bIa*xM8AdGeuN+q@zap$D!-BI5KB+L?FLAJY$}2QiwwgwHRQ(X>yQW@q0R!lw#( z=lgR%h%!Ug!;!ZE$B|3X@KcclVN`-+UT9DTOeJar?a}w(b*RA$=##sdbb2xUrKL;* zgLFh&3ht2_5r$&}s_l>Y9qz;L`6|&v?nqeQNI%f+?yuDg-BnZ%wJ;6cO=S~XvfEeC zMB0ERlwreEQ);{SWMK5W(_7@`-_FxkAsZM_m9uvZi3dZLOlwrh$NcnyHq#2DF3;B; zB7S}*68X|t=s?1>TFX57hPvz6e!mut-R$(M_u0sYlJkl+wV=B{cP?(Kc;M_`JnufX z(Y-JjO>*EOzSy9fY98fIzCgOX;avBeWF(M~2|;J|zXkU%{jcBjZ=wF{2>%w~{yq8` z=v)4U@K>}|Ui6;`*CQyG5!&BW3wqT5i2%lHuS5J>sO2}r6YwJNUji?eAXvbEyYKym z0DvQ~)8B7@*T?>+(|mpGChpbP|9b8F=Txp+>(bBtO@253=Jm=*pxwA^L> config.py +echo 'keypass = "r9aquRHYoI8+dYz6jKrLntQ5/NJNASFBacJh7Jv2BlI="' >> config.py +echo "accepted_formats = ['txt']" >> config.py +sed -i '/allow_disabled_algorithms/d' config.py +test -d metadata || mkdir metadata +cp $WORKSPACE/tests/metadata/*.txt metadata/ +echo 'Summary:good test version of urzip' > metadata/info.guardianproject.urzip.txt +echo 'Summary:good MD5 sig, which is disabled algorithm' > metadata/org.bitbucket.tickytacky.mirrormirror.txt +sed -i '/Archive Policy:/d' metadata/*.txt +test -d repo || mkdir repo +cp $WORKSPACE/tests/urzip.apk \ + $WORKSPACE/tests/org.bitbucket.tickytacky.mirrormirror_[0-9].apk \ + $WORKSPACE/tests/repo/com.politedroid_[0-9].apk \ + $WORKSPACE/tests/repo/obb.main.twoversions_110161[357].apk \ + repo/ +sed -i 's,archive_older = [0-9],archive_older = 3,' config.py + +$fdroid update --pretty --nosign +test `grep '' archive/index.xml | wc -l` -eq 5 +test `grep '' repo/index.xml | wc -l` -eq 7 + + #------------------------------------------------------------------------------# echo_header 'test per-app "Archive Policy"' @@ -383,6 +412,77 @@ test -e repo/com.politedroid_5.apk ! test -e repo/com.politedroid_6.apk +#------------------------------------------------------------------------------# +echo_header 'test allowing disabled signatures in repo and archive' + +REPOROOT=`create_test_dir` +cd $REPOROOT +cp $WORKSPACE/tests/keystore.jks $REPOROOT/ +$fdroid init --keystore keystore.jks --repo-keyalias=sova +echo 'keystorepass = "r9aquRHYoI8+dYz6jKrLntQ5/NJNASFBacJh7Jv2BlI="' >> config.py +echo 'keypass = "r9aquRHYoI8+dYz6jKrLntQ5/NJNASFBacJh7Jv2BlI="' >> config.py +echo "accepted_formats = ['txt']" >> config.py +echo 'allow_disabled_algorithms = True' >> config.py +sed -i 's,archive_older = [0-9],archive_older = 3,' config.py +test -d metadata || mkdir metadata +cp $WORKSPACE/tests/metadata/com.politedroid.txt metadata/ +echo 'Summary:good test version of urzip' > metadata/info.guardianproject.urzip.txt +echo 'Summary:good MD5 sig, disabled algorithm' > metadata/org.bitbucket.tickytacky.mirrormirror.txt +sed -i '/Archive Policy:/d' metadata/*.txt +test -d repo || mkdir repo +cp $WORKSPACE/tests/repo/com.politedroid_[0-9].apk \ + $WORKSPACE/tests/org.bitbucket.tickytacky.mirrormirror_[0-9].apk \ + $WORKSPACE/tests/urzip-badsig.apk \ + repo/ + +$fdroid update --pretty --nosign +test `grep '' archive/index.xml | wc -l` -eq 2 +test `grep '' repo/index.xml | wc -l` -eq 6 +grep -F com.politedroid_3.apk archive/index.xml +grep -F com.politedroid_4.apk repo/index.xml +grep -F com.politedroid_5.apk repo/index.xml +grep -F com.politedroid_6.apk repo/index.xml +grep -F org.bitbucket.tickytacky.mirrormirror_1.apk archive/index.xml +grep -F org.bitbucket.tickytacky.mirrormirror_2.apk repo/index.xml +grep -F org.bitbucket.tickytacky.mirrormirror_3.apk repo/index.xml +grep -F org.bitbucket.tickytacky.mirrormirror_4.apk repo/index.xml +! grep -F urzip-badsig.apk repo/index.xml +! grep -F urzip-badsig.apk archive/index.xml +test -e archive/com.politedroid_3.apk +test -e repo/com.politedroid_4.apk +test -e repo/com.politedroid_5.apk +test -e repo/com.politedroid_6.apk +test -e archive/org.bitbucket.tickytacky.mirrormirror_1.apk +test -e repo/org.bitbucket.tickytacky.mirrormirror_2.apk +test -e repo/org.bitbucket.tickytacky.mirrormirror_3.apk +test -e repo/org.bitbucket.tickytacky.mirrormirror_4.apk +test -e archive/urzip-badsig.apk + +sed -i '/allow_disabled_algorithms/d' config.py +$fdroid update --pretty --nosign +test `grep '' archive/index.xml | wc -l` -eq 5 +test `grep '' repo/index.xml | wc -l` -eq 3 +grep -F org.bitbucket.tickytacky.mirrormirror_1.apk archive/index.xml +grep -F org.bitbucket.tickytacky.mirrormirror_2.apk archive/index.xml +grep -F org.bitbucket.tickytacky.mirrormirror_3.apk archive/index.xml +grep -F org.bitbucket.tickytacky.mirrormirror_4.apk archive/index.xml +grep -F com.politedroid_3.apk archive/index.xml +grep -F com.politedroid_4.apk repo/index.xml +grep -F com.politedroid_5.apk repo/index.xml +grep -F com.politedroid_6.apk repo/index.xml +! grep -F urzip-badsig.apk repo/index.xml +! grep -F urzip-badsig.apk archive/index.xml +test -e archive/org.bitbucket.tickytacky.mirrormirror_1.apk +test -e archive/org.bitbucket.tickytacky.mirrormirror_2.apk +test -e archive/org.bitbucket.tickytacky.mirrormirror_3.apk +test -e archive/org.bitbucket.tickytacky.mirrormirror_4.apk +test -e archive/com.politedroid_3.apk +test -e archive/urzip-badsig.apk +test -e repo/com.politedroid_4.apk +test -e repo/com.politedroid_5.apk +test -e repo/com.politedroid_6.apk + + #------------------------------------------------------------------------------# echo_header 'rename apks with `fdroid update --rename-apks`, --nosign for speed' diff --git a/tests/update.TestCase b/tests/update.TestCase index cd47e020..91406a35 100755 --- a/tests/update.TestCase +++ b/tests/update.TestCase @@ -201,6 +201,7 @@ class UpdateTest(unittest.TestCase): fdroidserver.update.options.clean = True fdroidserver.update.options.delete_unknown = True fdroidserver.update.options.rename_apks = False + fdroidserver.update.options.allow_disabled_algorithms = False apps = fdroidserver.metadata.read_metadata(xref=True) knownapks = fdroidserver.common.KnownApks() @@ -250,7 +251,7 @@ class UpdateTest(unittest.TestCase): config = dict() fdroidserver.common.fill_config_defaults(config) fdroidserver.update.config = config - os.chdir(os.path.dirname(__file__)) + os.chdir(os.path.join(localmodule, 'tests')) if os.path.basename(os.getcwd()) != 'tests': raise Exception('This test must be run in the "tests/" subdir') @@ -263,6 +264,7 @@ class UpdateTest(unittest.TestCase): fdroidserver.update.options.clean = True fdroidserver.update.options.rename_apks = False fdroidserver.update.options.delete_unknown = True + fdroidserver.update.options.allow_disabled_algorithms = False for icon_dir in fdroidserver.update.get_all_icon_dirs('repo'): if not os.path.exists(icon_dir): @@ -290,6 +292,87 @@ class UpdateTest(unittest.TestCase): self.maxDiff = None self.assertEqual(apk, frompickle) + def test_scan_apk_signed_by_disabled_algorithms(self): + os.chdir(os.path.join(localmodule, 'tests')) + if os.path.basename(os.getcwd()) != 'tests': + raise Exception('This test must be run in the "tests/" subdir') + + config = dict() + fdroidserver.common.fill_config_defaults(config) + fdroidserver.update.config = config + + config['ndk_paths'] = dict() + config['accepted_formats'] = ['json', 'txt', 'yml'] + fdroidserver.common.config = config + fdroidserver.update.config = config + + fdroidserver.update.options = type('', (), {})() + fdroidserver.update.options.clean = True + fdroidserver.update.options.verbose = True + fdroidserver.update.options.rename_apks = False + fdroidserver.update.options.delete_unknown = True + fdroidserver.update.options.allow_disabled_algorithms = False + + knownapks = fdroidserver.common.KnownApks() + apksourcedir = os.getcwd() + tmpdir = os.path.join(localmodule, '.testfiles') + if not os.path.exists(tmpdir): + os.makedirs(tmpdir) + tmptestsdir = tempfile.mkdtemp(prefix='test_scan_apk_signed_by_disabled_algorithms-', dir=tmpdir) + print('tmptestsdir', tmptestsdir) + os.chdir(tmptestsdir) + os.mkdir('repo') + os.mkdir('archive') + # setup the repo, create icons dirs, etc. + fdroidserver.update.scan_apks({}, 'repo', knownapks) + fdroidserver.update.scan_apks({}, 'archive', knownapks) + + disabledsigs = ['org.bitbucket.tickytacky.mirrormirror_2.apk', ] + for apkName in disabledsigs: + shutil.copy(os.path.join(apksourcedir, apkName), + os.path.join(tmptestsdir, 'repo')) + + skip, apk, cachechanged = fdroidserver.update.scan_apk({}, apkName, 'repo', knownapks, + allow_disabled_algorithms=True, + archive_bad_sig=False) + self.assertFalse(skip) + self.assertIsNotNone(apk) + self.assertTrue(cachechanged) + self.assertFalse(os.path.exists(os.path.join('archive', apkName))) + self.assertTrue(os.path.exists(os.path.join('repo', apkName))) + + # this test only works on systems with fully updated Java/jarsigner + # that has MD5 listed in jdk.jar.disabledAlgorithms in java.security + skip, apk, cachechanged = fdroidserver.update.scan_apk({}, apkName, 'repo', knownapks, + allow_disabled_algorithms=False, + archive_bad_sig=True) + self.assertTrue(skip) + self.assertIsNone(apk) + self.assertFalse(cachechanged) + self.assertTrue(os.path.exists(os.path.join('archive', apkName))) + self.assertFalse(os.path.exists(os.path.join('repo', apkName))) + + skip, apk, cachechanged = fdroidserver.update.scan_apk({}, apkName, 'archive', knownapks, + allow_disabled_algorithms=False, + archive_bad_sig=False) + self.assertFalse(skip) + self.assertIsNotNone(apk) + self.assertTrue(cachechanged) + self.assertTrue(os.path.exists(os.path.join('archive', apkName))) + self.assertFalse(os.path.exists(os.path.join('repo', apkName))) + + badsigs = ['urzip-badcert.apk', 'urzip-badsig.apk', 'urzip-release-unsigned.apk', ] + for apkName in badsigs: + shutil.copy(os.path.join(apksourcedir, apkName), + os.path.join(tmptestsdir, 'repo')) + + skip, apk, cachechanged = fdroidserver.update.scan_apk({}, apkName, 'repo', knownapks, + allow_disabled_algorithms=False, + archive_bad_sig=False) + self.assertTrue(skip) + self.assertIsNone(apk) + self.assertFalse(cachechanged) + def test_scan_invalid_apk(self): os.chdir(os.path.join(localmodule, 'tests')) if os.path.basename(os.getcwd()) != 'tests': From 39fd6647780f8c0c1089c69f89bb0281895ab807 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Tue, 27 Jun 2017 22:07:53 +0200 Subject: [PATCH 06/10] update: create 'archive/' if needed when moving APKs Normally, just 'repo/' is created by default, e.g. `fdroid init`. If APKs are dumped into 'repo/', then have invalid signatures, then they'll be automatically moved to 'archive/', which therefore needs to exist. --- fdroidserver/update.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fdroidserver/update.py b/fdroidserver/update.py index 8488574f..151d6552 100644 --- a/fdroidserver/update.py +++ b/fdroidserver/update.py @@ -1493,6 +1493,8 @@ def move_apk_between_sections(from_dir, to_dir, apk): if ignore_missing and not os.path.exists(from_path): return to_path = os.path.join(to_dir, filename) + if not os.path.exists(to_dir): + os.mkdir(to_dir) shutil.move(from_path, to_path) if from_dir == to_dir: From 5f0817d7bf0d651cc566861f5c5cf2f0476fbbe8 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Wed, 28 Jun 2017 22:10:43 +0200 Subject: [PATCH 07/10] tests: make sure apkcache gets created --- tests/run-tests | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/run-tests b/tests/run-tests index b8514eef..625402e8 100755 --- a/tests/run-tests +++ b/tests/run-tests @@ -141,6 +141,8 @@ $fdroid signindex --verbose test -e repo/index.xml test -e repo/index.jar test -e repo/index-v1.jar +test -e tmp/apkcache +! test -z tmp/apkcache test -L urzip.apk grep -F ' Date: Thu, 29 Jun 2017 21:15:30 +0200 Subject: [PATCH 09/10] update: invalidate cache if allow_disabled_algorithms changes Since the cache contains implicitly the result of the jarsigner verify, if the allow_disabled_algorithms config changes, then the apkcache is invalid. --- fdroidserver/update.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/fdroidserver/update.py b/fdroidserver/update.py index 1e368f6e..6e77d88c 100644 --- a/fdroidserver/update.py +++ b/fdroidserver/update.py @@ -423,20 +423,35 @@ def get_cache_file(): def get_cache(): - """ + """Get the cached dict of the APK index + Gather information about all the apk files in the repo directory, - using cached data if possible. + using cached data if possible. Some of the index operations take a + long time, like calculating the SHA-256 and verifying the APK + signature. + + The cache is invalidated if the metadata version is different, or + the 'allow_disabled_algorithms' config/option is different. In + those cases, there is no easy way to know what has changed from + the cache, so just rerun the whole thing. + :return: apkcache + """ apkcachefile = get_cache_file() + ada = options.allow_disabled_algorithms or config['allow_disabled_algorithms'] if not options.clean and os.path.exists(apkcachefile): with open(apkcachefile, 'rb') as cf: apkcache = pickle.load(cf, encoding='utf-8') - if apkcache.get("METADATA_VERSION") != METADATA_VERSION: + if apkcache.get("METADATA_VERSION") != METADATA_VERSION \ + or apkcache.get('allow_disabled_algorithms') != ada: apkcache = {} else: apkcache = {} + apkcache["METADATA_VERSION"] = METADATA_VERSION + apkcache['allow_disabled_algorithms'] = ada + return apkcache @@ -445,7 +460,6 @@ def write_cache(apkcache): cache_path = os.path.dirname(apkcachefile) if not os.path.exists(cache_path): os.makedirs(cache_path) - apkcache["METADATA_VERSION"] = METADATA_VERSION with open(apkcachefile, 'wb') as cf: pickle.dump(apkcache, cf) From f2432f7fa49eebb36b7453c86941aee14d89c54b Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Thu, 29 Jun 2017 19:53:15 +0200 Subject: [PATCH 10/10] gitlab-ci: apt upgrade so that tests run with current updates The MD5 signature stuff was failing in tests because the CI image was using a quite old version of Java's jarsigner, which had not yet disabled MD5. --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 571253af..d0e173d8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,6 +2,7 @@ image: registry.gitlab.com/fdroid/ci-images:server-latest test: script: + - apt-get -qq update && apt-get -y dist-upgrade - mkdir -p /usr/lib/python3.4/site-packages/ # workaround https://github.com/pypa/setuptools/issues/937 - pip3 install setuptools==33.1.1