chiark / gitweb /
sort index-v1; publish now creates and stores a list of signature fingerprints
authorMichael Pöhn <michael.poehn@fsfe.org>
Tue, 19 Sep 2017 22:16:13 +0000 (00:16 +0200)
committerMichael Pöhn <michael.poehn@fsfe.org>
Tue, 26 Sep 2017 12:11:09 +0000 (14:11 +0200)
fdroidserver/index.py
fdroidserver/publish.py
fdroidserver/update.py
tests/index.TestCase

index 79322d55185da1c472b01eecabd10e5e21c6688f..c4fb6e6b89caf30d1677d3a187d1a67b8394d11f 100644 (file)
@@ -39,7 +39,7 @@ from . import common
 from . import metadata
 from . import net
 from . import signindex
-from fdroidserver.common import FDroidPopen, FDroidPopenBytes
+from fdroidserver.common import FDroidPopen, FDroidPopenBytes, load_stats_fdroid_signing_key_fingerprints
 from fdroidserver.exception import FDroidException, VerificationException, MetaDataException
 
 
@@ -151,11 +151,15 @@ def make(apps, sortedids, apks, repodir, archive):
                 raise TypeError(_('only accepts strings, lists, and tuples'))
         requestsdict[command] = packageNames
 
-    make_v0(appsWithPackages, apks, repodir, repodict, requestsdict)
-    make_v1(appsWithPackages, apks, repodir, repodict, requestsdict)
+    fdroid_signing_key_fingerprints = load_stats_fdroid_signing_key_fingerprints()
 
+    make_v0(appsWithPackages, apks, repodir, repodict, requestsdict,
+            fdroid_signing_key_fingerprints)
+    make_v1(appsWithPackages, apks, repodir, repodict, requestsdict,
+            fdroid_signing_key_fingerprints)
 
-def make_v1(apps, packages, repodir, repodict, requestsdict):
+
+def make_v1(apps, packages, repodir, repodict, requestsdict, fdroid_signing_key_fingerprints):
 
     def _index_encoder_default(obj):
         if isinstance(obj, set):
@@ -168,6 +172,9 @@ def make_v1(apps, packages, repodir, repodict, requestsdict):
     output['repo'] = repodict
     output['requests'] = requestsdict
 
+    # establish sort order of the index
+    v1_sort_packages(packages, repodir, fdroid_signing_key_fingerprints)
+
     appslist = []
     output['apps'] = appslist
     for packageName, appdict in apps.items():
@@ -234,6 +241,35 @@ def make_v1(apps, packages, repodir, repodict, requestsdict):
         signindex.sign_index_v1(repodir, json_name)
 
 
+def v1_sort_packages(packages, repodir, fdroid_signing_key_fingerprints):
+
+    GROUP_DEV_SIGNED = 1
+    GROUP_FDROID_SIGNED = 2
+    GROUP_OTHER_SIGNED = 3
+
+    def v1_sort_keys(package):
+        packageName = package.get('packageName', None)
+
+        sig = package.get('signer', None)
+
+        dev_sig = common.metadata_find_developer_signature(packageName)
+        group = GROUP_OTHER_SIGNED
+        if dev_sig and dev_sig == sig:
+            group = GROUP_DEV_SIGNED
+        else:
+            fdroidsig = fdroid_signing_key_fingerprints.get(packageName, {}).get('signer')
+            if fdroidsig and fdroidsig == sig:
+                group = GROUP_FDROID_SIGNED
+
+        versionCode = None
+        if package.get('versionCode', None):
+            versionCode = -int(package['versionCode'])
+
+        return(packageName, group, sig, versionCode)
+
+    packages.sort(key=v1_sort_keys)
+
+
 def make_v0(apps, apks, repodir, repodict, requestsdict):
     """
     aka index.jar aka index.xml
index c1c5b098cf45b86911e627f22447343cb9e9b458..4bdb8fb1a6498a313c4f8394e44a7b280962f0e6 100644 (file)
@@ -53,7 +53,7 @@ def publish_source_tarball(apkfilename, unsigned_dir, output_dir):
 
 
 def key_alias(appid, resolve=False):
-    """Get the alias which which F-Droid uses to indentify the singing key
+    """Get the alias which F-Droid uses to indentify the singing key
     for this App in F-Droids keystore.
     """
     if config and 'keyaliases' in config and appid in config['keyaliases']:
@@ -356,6 +356,9 @@ def main():
                 publish_source_tarball(apkfilename, unsigned_dir, output_dir)
                 logging.info('Published ' + apkfilename)
 
+    store_stats_fdroid_signing_key_fingerprints(allapps.keys())
+    logging.info('published list signing-key fingerprints')
+
 
 if __name__ == "__main__":
     main()
index 3a74b428f40835e1ef34d362f65ffe69b9e0535f..96a48b6abb8529f88bf20584040ee7b390c3d567 100644 (file)
@@ -971,13 +971,15 @@ def scan_apk(apk_file):
     else:
         scan_apk_androguard(apk, apk_file)
 
-    # Get the signature
+    # Get the signature, or rather the signing key fingerprints
     logging.debug('Getting signature of {0}'.format(os.path.basename(apk_file)))
     apk['sig'] = getsig(apk_file)
     if not apk['sig']:
         raise BuildException("Failed to get apk signature")
     apk['signer'] = common.apk_signer_fingerprint(os.path.join(os.getcwd(),
                                                                apk_file))
+    if not apk.get('signer'):
+        raise BuildException("Failed to get apk signing key fingerprint")
 
     # Get size of the APK
     apk['size'] = os.path.getsize(apk_file)
index 315935237ac395dc8584852e7a6b4bc24e96cd08..da47036f79ad26154d514015bc2e7f57f9c53ba2 100755 (executable)
@@ -7,8 +7,10 @@ import sys
 import unittest
 import zipfile
 from unittest.mock import patch
-
 import requests
+import tempfile
+import json
+import shutil
 
 localmodule = os.path.realpath(
     os.path.join(os.path.dirname(inspect.getfile(inspect.currentframe())), '..'))
@@ -19,6 +21,8 @@ if localmodule not in sys.path:
 import fdroidserver.common
 import fdroidserver.index
 import fdroidserver.signindex
+import fdroidserver.publish
+from testcommon import TmpCwd
 
 
 GP_FINGERPRINT = 'B7C2EEFD8DAC7806AF67DFCD92EB18126BC08312A7F2D6F3862E46013C7A6135'
@@ -114,8 +118,117 @@ class IndexTest(unittest.TestCase):
         self.assertEqual(10, len(index['packages']))
         self.assertEqual('new_etag', new_etag)
 
+    def test_v1_sort_packages(self):
+
+        i = [{'packageName': 'org.smssecure.smssecure',
+              'apkName': 'org.smssecure.smssecure_134.apk',
+              'signer': 'b33a601a9da97c82e6eb121eb6b90adab561f396602ec4dc8b0019fb587e2af6',
+              'versionCode': 134},
+             {'packageName': 'org.smssecure.smssecure',
+              'apkName': 'org.smssecure.smssecure_134_b30bb97.apk',
+              'signer': 'b30bb971af0d134866e158ec748fcd553df97c150f58b0a963190bbafbeb0868',
+              'versionCode': 134},
+             {'packageName': 'b075b32b4ef1e8a869e00edb136bd48e34a0382b85ced8628f164d1199584e4e'},
+             {'packageName': '43af70d1aca437c2f9974c4634cc5abe45bdc4d5d71529ac4e553488d3bb3ff6'},
+             {'packageName': 'org.smssecure.smssecure',
+              'apkName': 'org.smssecure.smssecure_135_b30bb97.apk',
+              'signer': 'b30bb971af0d134866e158ec748fcd553df97c150f58b0a963190bbafbeb0868',
+              'versionCode': 135},
+             {'packageName': 'org.smssecure.smssecure',
+              'apkName': 'org.smssecure.smssecure_135.apk',
+              'signer': 'b33a601a9da97c82e6eb121eb6b90adab561f396602ec4dc8b0019fb587e2af6',
+              'versionCode': 135},
+             {'packageName': 'org.smssecure.smssecure',
+              'apkName': 'org.smssecure.smssecure_133.apk',
+              'signer': 'b33a601a9da97c82e6eb121eb6b90adab561f396602ec4dc8b0019fb587e2af6',
+              'versionCode': 133},
+             {'packageName': 'org.smssecure.smssecure',
+              'apkName': 'smssecure-weird-version.apk',
+              'signer': '99ff99ff99ff99ff99ff99ff99ff99ff99ff99ff99ff99ff99ff99ff99ff99ff',
+              'versionCode': 133},
+             {'packageName': 'org.smssecure.smssecure',
+              'apkName': 'smssecure-custom.apk',
+              'signer': '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
+              'versionCode': 133},
+             {'packageName': 'org.smssecure.smssecure',
+              'apkName': 'smssecure-new-custom.apk',
+              'signer': '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
+              'versionCode': 135}]
+
+        o = [{'packageName': '43af70d1aca437c2f9974c4634cc5abe45bdc4d5d71529ac4e553488d3bb3ff6'},
+             {'packageName': 'b075b32b4ef1e8a869e00edb136bd48e34a0382b85ced8628f164d1199584e4e'},
+             # app test data
+             # # packages with reproducible developer signature
+             {'packageName': 'org.smssecure.smssecure',
+              'apkName': 'org.smssecure.smssecure_135_b30bb97.apk',
+              'signer': 'b30bb971af0d134866e158ec748fcd553df97c150f58b0a963190bbafbeb0868',
+              'versionCode': 135},
+             {'packageName': 'org.smssecure.smssecure',
+              'apkName': 'org.smssecure.smssecure_134_b30bb97.apk',
+              'signer': 'b30bb971af0d134866e158ec748fcd553df97c150f58b0a963190bbafbeb0868',
+              'versionCode': 134},
+             # # packages build and signed by fdroid
+             {'packageName': 'org.smssecure.smssecure',
+              'apkName': 'org.smssecure.smssecure_135.apk',
+              'signer': 'b33a601a9da97c82e6eb121eb6b90adab561f396602ec4dc8b0019fb587e2af6',
+              'versionCode': 135},
+             {'packageName': 'org.smssecure.smssecure',
+              'apkName': 'org.smssecure.smssecure_134.apk',
+              'signer': 'b33a601a9da97c82e6eb121eb6b90adab561f396602ec4dc8b0019fb587e2af6',
+              'versionCode': 134},
+             {'packageName': 'org.smssecure.smssecure',
+              'apkName': 'org.smssecure.smssecure_133.apk',
+              'signer': 'b33a601a9da97c82e6eb121eb6b90adab561f396602ec4dc8b0019fb587e2af6',
+              'versionCode': 133},
+             # # packages signed with unkown keys
+             {'packageName': 'org.smssecure.smssecure',
+              'apkName': 'smssecure-new-custom.apk',
+              'signer': '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
+              'versionCode': 135},
+             {'packageName': 'org.smssecure.smssecure',
+              'apkName': 'smssecure-custom.apk',
+              'signer': '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
+              'versionCode': 133},
+             {'packageName': 'org.smssecure.smssecure',
+              'apkName': 'smssecure-weird-version.apk',
+              'signer': '99ff99ff99ff99ff99ff99ff99ff99ff99ff99ff99ff99ff99ff99ff99ff99ff',
+              'versionCode': 133}]
+
+        fdroidserver.common.config = {}
+        fdroidserver.common.fill_config_defaults(fdroidserver.common.config)
+        fdroidserver.publish.config = fdroidserver.common.config
+        fdroidserver.publish.config['keystorepass'] = '123456'
+        fdroidserver.publish.config['keypass'] = '123456'
+        fdroidserver.publish.config['keystore'] = os.path.join(os.getcwd(),
+                                                               'dummy-keystore.jks')
+        fdroidserver.publish.config['repo_keyalias'] = 'repokey'
+
+        testsmetadir = os.path.join(os.getcwd(), 'metadata')
+        with tempfile.TemporaryDirectory() as tmpdir, TmpCwd(tmpdir):
+            shutil.copytree(testsmetadir, 'metadata')
+            sigkeyfps = {
+                "org.smssecure.smssecure": {
+                    "signer": "b33a601a9da97c82e6eb121eb6b90adab561f396602ec4dc8b0019fb587e2af6"
+                }
+            }
+            os.makedirs('repo')
+            jarfile = 'repo/publishsigkeys.jar'
+            with zipfile.ZipFile(jarfile, 'w', zipfile.ZIP_DEFLATED) as jar:
+                jar.writestr('publishsigkeys.json', json.dumps(sigkeyfps))
+            fdroidserver.publish.sign_sig_key_fingerprint_list(jarfile)
+            with open('config.py', 'w'):
+                pass
+
+            fdroidserver.index.v1_sort_packages(
+                i, 'repo', fdroidserver.common.load_stats_fdroid_signing_key_fingerprints())
+            self.maxDiff = None
+            self.assertEqual(json.dumps(i, indent=2), json.dumps(o, indent=2))
+
 
 if __name__ == "__main__":
+    if os.path.basename(os.getcwd()) != 'tests' and os.path.isdir('tests'):
+        os.chdir('tests')
+
     parser = optparse.OptionParser()
     parser.add_option("-v", "--verbose", action="store_true", default=False,
                       help="Spew out even more information than normal")