chiark / gitweb /
fix bare except to satisfy newer pycodestyle
[fdroidserver.git] / fdroidserver / common.py
index 2857ac06323489b1f4f7f0822185f18707e486d2..794504e311798a44a3b033caa73c1df40a80addf 100644 (file)
@@ -36,6 +36,7 @@ import socket
 import base64
 import zipfile
 import tempfile
+import json
 import xml.etree.ElementTree as XMLElementTree
 
 from binascii import hexlify
@@ -511,11 +512,23 @@ def publishednameinfo(filename):
     return result
 
 
-apk_release_filename = re.compile('(?P<appid>[a-z0-9_\.]+)_(?P<vercode>[0-9]+)\.apk')
-apk_release_filename_with_sigfp = re.compile('(?P<appid>[a-z0-9_\.]+)_(?P<vercode>[0-9]+)_(?P<sigfp>[0-9a-f]{7})\.apk')
+apk_release_filename = re.compile('(?P<appid>[a-zA-Z0-9_\.]+)_(?P<vercode>[0-9]+)\.apk')
+apk_release_filename_with_sigfp = re.compile('(?P<appid>[a-zA-Z0-9_\.]+)_(?P<vercode>[0-9]+)_(?P<sigfp>[0-9a-f]{7})\.apk')
 
 
 def apk_parse_release_filename(apkname):
+    """Parses the name of an APK file according the F-Droids APK naming
+    scheme and returns the tokens.
+
+    WARNING: Returned values don't necessarily represent the APKs actual
+    properties, the are just paresed from the file name.
+
+    :returns: A triplet containing (appid, versionCode, signer), where appid
+        should be the package name, versionCode should be the integer
+        represion of the APKs version and signer should be the first 7 hex
+        digists of the sha256 signing key fingerprint which was used to sign
+        this APK.
+    """
     m = apk_release_filename_with_sigfp.match(apkname)
     if m:
         return m.group('appid'), m.group('vercode'), m.group('sigfp')
@@ -2036,7 +2049,7 @@ def signer_fingerprint_short(sig):
     Extracts the first 7 hexadecimal digits of sha256 signing-key fingerprint
     for a given pkcs7 signature.
 
-    :param sig: Contents of an APK signature.
+    :param sig: Contents of an APK signing certificate.
     :returns: shortened signing-key fingerprint.
     """
     return signer_fingerprint(sig)[:7]
@@ -2099,6 +2112,40 @@ def metadata_get_sigdir(appid, vercode=None):
         return os.path.join('metadata', appid, 'signatures')
 
 
+def metadata_find_developer_signature(appid, vercode=None):
+    """Tires to find the developer signature for given appid.
+
+    This picks the first signature file found in metadata an returns its
+    signature.
+
+    :returns: sha256 signing key fingerprint of the developer signing key.
+        None in case no signature can not be found."""
+
+    # fetch list of dirs for all versions of signatures
+    appversigdirs = []
+    if vercode:
+        appversigdirs.append(metadata_get_sigdir(appid, vercode))
+    else:
+        appsigdir = metadata_get_sigdir(appid)
+        if os.path.isdir(appsigdir):
+            numre = re.compile('[0-9]+')
+            for ver in os.listdir(appsigdir):
+                if numre.match(ver):
+                    appversigdir = os.path.join(appsigdir, ver)
+                    appversigdirs.append(appversigdir)
+
+    for sigdir in appversigdirs:
+        sigs = glob.glob(os.path.join(sigdir, '*.DSA')) + \
+            glob.glob(os.path.join(sigdir, '*.EC')) + \
+            glob.glob(os.path.join(sigdir, '*.RSA'))
+        if len(sigs) > 1:
+            raise FDroidException('ambiguous signatures, please make sure there is only one signature in \'{}\'. (The signature has to be the App maintainers signature for version of the APK.)'.format(sigdir))
+        for sig in sigs:
+            with open(sig, 'rb') as f:
+                return signer_fingerprint(f.read())
+    return None
+
+
 def metadata_find_signing_files(appid, vercode):
     """Gets a list of singed manifests and signatures.
 
@@ -2159,7 +2206,7 @@ def apk_strip_signatures(signed_apk, strip_manifest=False):
 
 
 def apk_implant_signatures(apkpath, signaturefile, signedfile, manifest):
-    """Implats a signature from out metadata into an APK.
+    """Implats a signature from metadata into an APK.
 
     Note: this changes there supplied APK in place. So copy it if you
     need the original to be preserved.
@@ -2301,7 +2348,7 @@ def verify_apk_signature(apk, min_sdk_version=None):
         try:
             verify_jar_signature(apk)
             return True
-        except:
+        except Exception:
             pass
     return False
 
@@ -2518,6 +2565,34 @@ def get_certificate(certificate_file):
     return encoder.encode(cert)
 
 
+def load_stats_fdroid_signing_key_fingerprints():
+    """Load list of signing-key fingerprints stored by fdroid publish from file.
+
+    :returns: list of dictionanryies containing the singing-key fingerprints.
+    """
+    jar_file = os.path.join('stats', 'publishsigkeys.jar')
+    if not os.path.isfile(jar_file):
+        return {}
+    cmd = [config['jarsigner'], '-strict', '-verify', jar_file]
+    p = FDroidPopen(cmd, output=False)
+    if p.returncode != 4:
+        raise FDroidException("Signature validation of '{}' failed! "
+                              "Please run publish again to rebuild this file.".format(jar_file))
+
+    jar_sigkey = apk_signer_fingerprint(jar_file)
+    repo_key_sig = config.get('repo_key_sha256')
+    if repo_key_sig:
+        if jar_sigkey != repo_key_sig:
+            raise FDroidException("Signature key fingerprint of file '{}' does not match repo_key_sha256 in config.py (found fingerprint: '{}')".format(jar_file, jar_sigkey))
+    else:
+        logging.warning("repo_key_sha256 not in config.py, setting it to the signature key fingerprint of '{}'".format(jar_file))
+        config['repo_key_sha256'] = jar_sigkey
+        write_to_config(config, 'repo_key_sha256')
+
+    with zipfile.ZipFile(jar_file, 'r') as f:
+        return json.loads(str(f.read('publishsigkeys.json'), 'utf-8'))
+
+
 def write_to_config(thisconfig, key, value=None, config_file=None):
     '''write a key/value to the local config.py