From 20caa6fa1c24e9569ee15d76aa0f05fb9a3c283d Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Wed, 15 Jan 2025 14:41:53 +0100 Subject: [PATCH] match the full file name when looking for the v1 signature block ZipFile.namelist() produces a string per file. The filename could contain newline chars, including at the beginning and end. ^$ in regex matches around newline chars. \A\Z matches the beginning/end of the full string. This is exactly the same as obfusk's r'\AMETA-INF/(?s:.)*\.(DSA|EC|RSA)\Z' but in a readable format that is also easily searchable, and standard for this code base. https://github.com/obfusk/fdroid-fakesigner-poc/blob/master/fdroidserver-regex.patch #1251 --- fdroidserver/common.py | 2 +- tests/test_common.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/fdroidserver/common.py b/fdroidserver/common.py index 5f812206..bf58433d 100644 --- a/fdroidserver/common.py +++ b/fdroidserver/common.py @@ -94,7 +94,7 @@ MINIMUM_APKSIGNER_BUILD_TOOLS_VERSION = '30.0.0' VERCODE_OPERATION_RE = re.compile(r'^([ 0-9/*+-]|%c)+$') # A signature block file with a .DSA, .RSA, or .EC extension -SIGNATURE_BLOCK_FILE_REGEX = re.compile(r'^META-INF/.*\.(DSA|EC|RSA)$') +SIGNATURE_BLOCK_FILE_REGEX = re.compile(r'\AMETA-INF/.*\.(DSA|EC|RSA)\Z', re.DOTALL) APK_NAME_REGEX = re.compile(r'^([a-zA-Z][\w.]*)_(-?[0-9]+)_?([0-9a-f]{7})?\.apk') APK_ID_TRIPLET_REGEX = re.compile(r"^package: name='(\w[^']*)' versionCode='([^']+)' versionName='([^']*)'") STANDARD_FILE_NAME_REGEX = re.compile(r'^(\w[\w.]*)_(-?[0-9]+)\.\w+') diff --git a/tests/test_common.py b/tests/test_common.py index 4ae55c73..144a61b0 100755 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -3253,6 +3253,34 @@ class SignerExtractionTest(unittest.TestCase): fdroidserver.common.signer_fingerprint(v3_certs[0]), ) + def test_signature_block_file_regex(self): + for apkpath, fingerprint in APKS_WITH_JAR_SIGNATURES: + with ZipFile(apkpath, 'r') as apk: + cert_files = [ + n + for n in apk.namelist() + if fdroidserver.common.SIGNATURE_BLOCK_FILE_REGEX.match(n) + ] + self.assertEqual(1, len(cert_files)) + + def test_signature_block_file_regex_malicious(self): + apkpath = os.path.join(self.testdir, 'malicious.apk') + with ZipFile(apkpath, 'w') as apk: + apk.writestr('META-INF/MANIFEST.MF', 'this is fake sig data') + apk.writestr('META-INF/CERT.SF\n', 'this is fake sig data') + apk.writestr('META-INF/AFTER.SF', 'this is fake sig data') + apk.writestr('META-INF/CERT.RSA\n', 'this is fake sig data') + apk.writestr('META-INF/AFTER.RSA', 'this is fake sig data') + with ZipFile(apkpath, 'r') as apk: + self.assertEqual( + ['META-INF/AFTER.RSA'], + [ + n + for n in apk.namelist() + if fdroidserver.common.SIGNATURE_BLOCK_FILE_REGEX.match(n) + ], + ) + class ConfigOptionsScopeTest(unittest.TestCase): """Test assumptions about variable scope for "config" and "options".