diff --git a/fdroidserver/common.py b/fdroidserver/common.py index 7160809e..3905c45e 100644 --- a/fdroidserver/common.py +++ b/fdroidserver/common.py @@ -3408,13 +3408,45 @@ def verify_apks(signed_apk, unsigned_apk, tmp_dir, v1_only=None): return None -def verify_deprecated_jar_signature(jar): +def verify_jar_signature(jar): """Verify the signature of a given JAR file. jarsigner is very shitty: unsigned JARs pass as "verified"! So this has to turn on -strict then check for result 4, since this does not expect the signature to be from a CA-signed certificate. + Raises + ------ + VerificationException + If the JAR's signature could not be verified. + + """ + error = _('JAR signature failed to verify: {path}').format(path=jar) + try: + output = subprocess.check_output( + [config['jarsigner'], '-strict', '-verify', jar], stderr=subprocess.STDOUT + ) + raise VerificationException(error + '\n' + output.decode('utf-8')) + except subprocess.CalledProcessError as e: + if e.returncode == 4: + logging.debug(_('JAR signature verified: {path}').format(path=jar)) + else: + raise VerificationException(error + '\n' + e.output.decode('utf-8')) from e + + +def verify_deprecated_jar_signature(jar): + """Verify the signature of a given JAR file, allowing deprecated algorithms. + + index.jar (v0) and index-v1.jar are both signed by MD5/SHA1 by + definition, so this method provides a way to verify those. Also, + apksigner has different deprecation rules than jarsigner, so this + is our current hack to try to represent the apksigner rules when + executing jarsigner. + + jarsigner is very shitty: unsigned JARs pass as "verified"! So + this has to turn on -strict then check for result 4, since this + does not expect the signature to be from a CA-signed certificate. + Also used to verify the signature on an archived APK, supporting deprecated algorithms. diff --git a/tests/common.TestCase b/tests/common.TestCase index d4658308..c3ca5dda 100755 --- a/tests/common.TestCase +++ b/tests/common.TestCase @@ -529,6 +529,35 @@ class CommonTest(unittest.TestCase): self.assertRaises(VerificationException, fdroidserver.common.verify_deprecated_jar_signature, 'urzip-badsig.apk') self.assertRaises(VerificationException, fdroidserver.common.verify_deprecated_jar_signature, 'urzip-release-unsigned.apk') + def test_verify_jar_signature(self): + """Sign entry.jar and make sure it validates""" + config = fdroidserver.common.read_config(fdroidserver.common.options) + config['jarsigner'] = fdroidserver.common.find_sdk_tools_cmd('jarsigner') + config['keystore'] = os.path.join(self.basedir, 'keystore.jks') + config['repo_keyalias'] = 'sova' + config['keystorepass'] = 'r9aquRHYoI8+dYz6jKrLntQ5/NJNASFBacJh7Jv2BlI=' + config['keypass'] = 'r9aquRHYoI8+dYz6jKrLntQ5/NJNASFBacJh7Jv2BlI=' + fdroidserver.common.config = config + fdroidserver.signindex.config = config + repo_dir = Path(self.testdir) / 'repo' + repo_dir.mkdir() + shutil.copy('repo/entry.json', repo_dir) + shutil.copy('repo/index-v2.json', repo_dir) + os.chdir(self.testdir) + fdroidserver.signindex.sign_index('repo', 'entry.json') + fdroidserver.common.verify_jar_signature('repo/entry.jar') + + def test_verify_jar_signature_fails(self): + """Test verify_jar_signature fails on unsigned and deprecated algorithms""" + config = fdroidserver.common.read_config(fdroidserver.common.options) + config['jarsigner'] = fdroidserver.common.find_sdk_tools_cmd('jarsigner') + fdroidserver.common.config = config + source_dir = os.path.join(self.basedir, 'signindex') + for f in ('unsigned.jar', 'testy.jar', 'guardianproject.jar', 'guardianproject-v1.jar'): + testfile = os.path.join(source_dir, f) + with self.assertRaises(fdroidserver.index.VerificationException): + fdroidserver.common.verify_jar_signature(testfile) + def test_verify_deprecated_jar_signature(self): config = fdroidserver.common.read_config(fdroidserver.common.options) config['jarsigner'] = fdroidserver.common.find_sdk_tools_cmd('jarsigner')