chiark / gitweb /
add common functions for dealing with apk signatures
authorMichael Pöhn <michael.poehn@fsfe.org>
Sat, 23 Sep 2017 07:36:22 +0000 (09:36 +0200)
committerMichael Pöhn <michael.poehn@fsfe.org>
Tue, 26 Sep 2017 12:11:09 +0000 (14:11 +0200)
fdroidserver/common.py
tests/common.TestCase

index 887203f3d86a3a29cbdcf1cf3744cc99c45d7d8f..c3c3534ced0e1cae6483c6e9c61e6c1ed6a59820 100644 (file)
@@ -35,6 +35,7 @@ import hashlib
 import socket
 import base64
 import zipfile
+import tempfile
 import xml.etree.ElementTree as XMLElementTree
 
 from binascii import hexlify
@@ -2015,6 +2016,18 @@ def place_srclib(root_dir, number, libpath):
 apk_sigfile = re.compile(r'META-INF/[0-9A-Za-z]+\.(SF|RSA|DSA|EC)')
 
 
+def signer_fingerprint_short(sig):
+    """Obtain shortened sha256 signing-key fingerprint for pkcs7 signature.
+
+    Extracts the first 7 hexadecimal digits of sha256 signing-key fingerprint
+    for a given pkcs7 signature.
+
+    :param sig: Contents of an APK signature.
+    :returns: shortened signing-key fingerprint.
+    """
+    return signer_fingerprint(sig)[:7]
+
+
 def signer_fingerprint(sig):
     """Obtain sha256 signing-key fingerprint for pkcs7 signature.
 
@@ -2052,6 +2065,18 @@ def apk_signer_fingerprint(apk_path):
         return signer_fingerprint(cert)
 
 
+def apk_signer_fingerprint_short(apk_path):
+    """Obtain shortened sha256 signing-key fingerprint for APK.
+
+    Extracts the first 7 hexadecimal digits of sha256 signing-key fingerprint
+    for a given pkcs7 APK.
+
+    :param apk_path: path to APK
+    :returns: shortened signing-key fingerprint
+    """
+    return apk_signer_fingerprint(apk_path)[:7]
+
+
 def metadata_get_sigdir(appid, vercode=None):
     """Get signature directory for app"""
     if vercode:
@@ -2060,6 +2085,94 @@ def metadata_get_sigdir(appid, vercode=None):
         return os.path.join('metadata', appid, 'signatures')
 
 
+def metadata_find_signing_files(appid, vercode):
+    """Gets a list of singed manifests and signatures.
+
+    :param appid: id string of that app
+    :param vercode: version code of that app
+    :returns: a list of triplets for each signing key with following paths:
+        (signature_file, singed_file, manifest_file)
+    """
+    ret = []
+    sigdir = metadata_get_sigdir(appid, vercode)
+    sigs = glob.glob(os.path.join(sigdir, '*.DSA')) + \
+        glob.glob(os.path.join(sigdir, '*.EC')) + \
+        glob.glob(os.path.join(sigdir, '*.RSA'))
+    extre = re.compile('(\.DSA|\.EC|\.RSA)$')
+    for sig in sigs:
+        sf = extre.sub('.SF', sig)
+        if os.path.isfile(sf):
+            mf = os.path.join(sigdir, 'MANIFEST.MF')
+            if os.path.isfile(mf):
+                ret.append((sig, sf, mf))
+    return ret
+
+
+def metadata_find_developer_signing_files(appid, vercode):
+    """Get developer signature files for specified app from metadata.
+
+    :returns: A triplet of paths for signing files from metadata:
+        (signature_file, singed_file, manifest_file)
+    """
+    allsigningfiles = metadata_find_signing_files(appid, vercode)
+    if allsigningfiles and len(allsigningfiles) == 1:
+        return allsigningfiles[0]
+    else:
+        return None
+
+
+def apk_strip_signatures(signed_apk, strip_manifest=False):
+    """Removes signatures from APK.
+
+    :param signed_apk: path to apk file.
+    :param strip_manifest: when set to True also the manifest file will
+        be removed from the APK.
+    """
+    with tempfile.TemporaryDirectory() as tmpdir:
+        tmp_apk = os.path.join(tmpdir, 'tmp.apk')
+        os.rename(signed_apk, tmp_apk)
+        with ZipFile(tmp_apk, 'r') as in_apk:
+            with ZipFile(signed_apk, 'w') as out_apk:
+                for f in in_apk.infolist():
+                    if not apk_sigfile.match(f.filename):
+                        if strip_manifest:
+                            if f.filename != 'META-INF/MANIFEST.MF':
+                                buf = in_apk.read(f.filename)
+                                out_apk.writestr(f.filename, buf)
+                        else:
+                            buf = in_apk.read(f.filename)
+                            out_apk.writestr(f.filename, buf)
+
+
+def apk_implant_signatures(apkpath, signaturefile, signedfile, manifest):
+    """Implats a signature from out metadata into an APK.
+
+    Note: this changes there supplied APK in place. So copy it if you
+    need the original to be preserved.
+
+    :param apkpath: location of the apk
+    """
+    # get list of available signature files in metadata
+    with tempfile.TemporaryDirectory() as tmpdir:
+        # orig_apk = os.path.join(tmpdir, 'orig.apk')
+        # os.rename(apkpath, orig_apk)
+        apkwithnewsig = os.path.join(tmpdir, 'newsig.apk')
+        with ZipFile(apkpath, 'r') as in_apk:
+            with ZipFile(apkwithnewsig, 'w') as out_apk:
+                for sig_file in [signaturefile, signedfile, manifest]:
+                    out_apk.write(sig_file, arcname='META-INF/' +
+                                  os.path.basename(sig_file))
+                for f in in_apk.infolist():
+                    if not apk_sigfile.match(f.filename):
+                        if f.filename != 'META-INF/MANIFEST.MF':
+                            buf = in_apk.read(f.filename)
+                            out_apk.writestr(f.filename, buf)
+        os.remove(apkpath)
+        p = SdkToolsPopen(['zipalign', '-v', '4', apkwithnewsig, apkpath])
+        if p.returncode != 0:
+            raise BuildException("Failed to align application")
+
+
 def apk_extract_signatures(apkpath, outdir, manifest=True):
     """Extracts a signature files from APK and puts them into target directory.
 
index dee8b667701d27ac34b4b97c78b469f281693306..623bb76d4345a0a02dd9c9cd3e3a44dbef07e59c 100755 (executable)
@@ -390,6 +390,17 @@ class CommonTest(unittest.TestCase):
             self.assertEqual(keytoolcertfingerprint,
                              fdroidserver.common.apk_signer_fingerprint(apkfile))
 
+    def test_apk_signer_fingerprint_short(self):
+
+        # fingerprints fetched with: keytool -printcert -file ____.RSA
+        testapks = (('repo/obb.main.oldversion_1444412523.apk', '818e469'),
+                    ('repo/obb.main.twoversions_1101613.apk', '32a2362'),
+                    ('repo/obb.main.twoversions_1101617.apk', '32a2362'))
+
+        for apkfile, keytoolcertfingerprint in testapks:
+            self.assertEqual(keytoolcertfingerprint,
+                             fdroidserver.common.apk_signer_fingerprint_short(apkfile))
+
 
 if __name__ == "__main__":
     parser = optparse.OptionParser()