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
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 acca01dd..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',
@@ -2041,6 +2042,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 1f322837..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)
@@ -1082,7 +1096,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 +1108,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.
"""
@@ -1184,12 +1202,29 @@ 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.
+ skipapk = False
if not common.verify_apk_signature(apkfile):
+ if repodir == 'archive' or allow_disabled_algorithms:
+ if common.verify_old_apk_signature(apkfile):
+ apk['antiFeatures'].update(['KnownVuln', 'DisabledAlgorithm'])
+ else:
+ skipapk = True
+ else:
+ skipapk = True
+
+ 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 has_known_vulnerability(apkfile):
- apk['antiFeatures'].add('KnownVuln')
+ if 'KnownVuln' not in apk['antiFeatures']:
+ if has_known_vulnerability(apkfile):
+ apk['antiFeatures'].add('KnownVuln')
apkzip = zipfile.ZipFile(apkfile, 'r')
@@ -1363,10 +1398,13 @@ 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, cachethis) = scan_apk(apkcache, apkfilename, repodir, knownapks,
+ use_date_from_apk, ada, True)
if skip:
continue
apks.append(apk)
+ cachechanged = cachechanged or cachethis
return apks, cachechanged
@@ -1421,6 +1459,15 @@ def make_categories_txt(repodir, categories):
def archive_old_apks(apps, apks, archapks, repodir, archivedir, 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)
+
for appid, app in apps.items():
if app.ArchivePolicy:
@@ -1428,60 +1475,57 @@ 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:]:
- 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)
+ for apk in current_app_apks[keepversions:]:
+ move_apk_between_sections(repodir, archivedir, apk)
archapks.append(apk)
apks.remove(apk)
- elif len(apks) < keepversions and len(archapks) > 0:
- required = keepversions - len(apks)
- archapklist = filter_apk_list_sorted(archapks)
- # Move forward the ones we want again.
- for apk in archapklist[: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)
- archapks.remove(apk)
- apks.append(apk)
+
+ current_app_archapks = filter_apk_list_sorted(archapks)
+ if len(current_app_apks) < keepversions and len(current_app_archapks) > 0:
+ 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):
+ """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)
+ if not os.path.exists(to_dir):
+ os.mkdir(to_dir)
+ 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)
+ 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):
@@ -1544,6 +1588,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/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/org.bitbucket.tickytacky.mirrormirror_1.apk b/tests/org.bitbucket.tickytacky.mirrormirror_1.apk
new file mode 100644
index 00000000..6ec4272f
Binary files /dev/null and b/tests/org.bitbucket.tickytacky.mirrormirror_1.apk differ
diff --git a/tests/org.bitbucket.tickytacky.mirrormirror_2.apk b/tests/org.bitbucket.tickytacky.mirrormirror_2.apk
new file mode 100644
index 00000000..26474277
Binary files /dev/null and b/tests/org.bitbucket.tickytacky.mirrormirror_2.apk differ
diff --git a/tests/org.bitbucket.tickytacky.mirrormirror_3.apk b/tests/org.bitbucket.tickytacky.mirrormirror_3.apk
new file mode 100644
index 00000000..af2a1fe8
Binary files /dev/null and b/tests/org.bitbucket.tickytacky.mirrormirror_3.apk differ
diff --git a/tests/org.bitbucket.tickytacky.mirrormirror_4.apk b/tests/org.bitbucket.tickytacky.mirrormirror_4.apk
new file mode 100644
index 00000000..a5f52eda
Binary files /dev/null and b/tests/org.bitbucket.tickytacky.mirrormirror_4.apk differ
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 00000000..19634ba9
Binary files /dev/null and b/tests/repo/com.politedroid_3.apk differ
diff --git a/tests/repo/com.politedroid_4.apk b/tests/repo/com.politedroid_4.apk
new file mode 100644
index 00000000..7ef46599
Binary files /dev/null and b/tests/repo/com.politedroid_4.apk differ
diff --git a/tests/repo/com.politedroid_5.apk b/tests/repo/com.politedroid_5.apk
new file mode 100644
index 00000000..35e0ed6c
Binary files /dev/null and b/tests/repo/com.politedroid_5.apk differ
diff --git a/tests/repo/com.politedroid_6.apk b/tests/repo/com.politedroid_6.apk
new file mode 100644
index 00000000..f48d8082
Binary files /dev/null and b/tests/repo/com.politedroid_6.apk differ
diff --git a/tests/repo/index.xml b/tests/repo/index.xml
index 4b85a960..9836fc3e 100644
--- a/tests/repo/index.xml
+++ b/tests/repo/index.xml
@@ -159,6 +159,71 @@
b4964fd759edaa54e65bb476d0276880
+
+ 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..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 '> 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"'
+
+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 "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'
+
+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
+
+
+#------------------------------------------------------------------------------#
+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
#------------------------------------------------------------------------------#
@@ -517,6 +762,8 @@ grep -F '