.format(apkfilename=apkfile))
+def get_minSdkVersion_aapt(apkfile):
+ """Extract the minimum supported Android SDK from an APK using aapt
+
+ :param apkfile: path to an APK file.
+ :returns: the integer representing the SDK version
+ """
+ r = re.compile(r"^sdkVersion:'([0-9]+)'")
+ p = SdkToolsPopen(['aapt', 'dump', 'badging', apkfile], output=False)
+ for line in p.output.splitlines():
+ m = r.match(line)
+ if m:
+ return int(m.group(1))
+ raise FDroidException(_('Reading minSdkVersion failed: "{apkfilename}"')
+ .format(apkfilename=apkfile))
+
+
class PopenResult:
def __init__(self):
self.returncode = None
out_file.write(in_apk.read(f.filename))
+def sign_apk(unsigned_path, signed_path, keyalias):
+ """Sign and zipalign an unsigned APK, then save to a new file, deleting the unsigned
+
+ android-18 (4.3) finally added support for reasonable hash
+ algorithms, like SHA-256, before then, the only options were MD5
+ and SHA1 :-/ This aims to use SHA-256 when the APK does not target
+ older Android versions, and is therefore safe to do so.
+
+ https://issuetracker.google.com/issues/36956587
+ https://android-review.googlesource.com/c/platform/libcore/+/44491
+
+ """
+
+ if get_minSdkVersion_aapt(unsigned_path) < 18:
+ signature_algorithm = ['-sigalg', 'SHA1withRSA', '-digestalg', 'SHA1']
+ else:
+ signature_algorithm = ['-sigalg', 'SHA256withRSA', '-digestalg', 'SHA256']
+
+ p = FDroidPopen([config['jarsigner'], '-keystore', config['keystore'],
+ '-storepass:env', 'FDROID_KEY_STORE_PASS',
+ '-keypass:env', 'FDROID_KEY_PASS']
+ + signature_algorithm + [unsigned_path, keyalias],
+ envs={
+ 'FDROID_KEY_STORE_PASS': config['keystorepass'],
+ 'FDROID_KEY_PASS': config['keypass'], })
+ if p.returncode != 0:
+ raise BuildException(_("Failed to sign application"), p.output)
+
+ p = SdkToolsPopen(['zipalign', '-v', '4', unsigned_path, signed_path])
+ if p.returncode != 0:
+ raise BuildException(_("Failed to zipalign application"))
+ os.remove(unsigned_path)
+
+
def verify_apks(signed_apk, unsigned_apk, tmp_dir):
"""Verify that two apks are the same
self.assertEqual(keytoolcertfingerprint,
fdroidserver.common.apk_signer_fingerprint_short(apkfile))
+ def test_sign_apk(self):
+ fdroidserver.common.config = None
+ config = fdroidserver.common.read_config(fdroidserver.common.options)
+ config['jarsigner'] = fdroidserver.common.find_sdk_tools_cmd('jarsigner')
+ config['keyalias'] = 'sova'
+ config['keystorepass'] = 'r9aquRHYoI8+dYz6jKrLntQ5/NJNASFBacJh7Jv2BlI='
+ config['keypass'] = 'r9aquRHYoI8+dYz6jKrLntQ5/NJNASFBacJh7Jv2BlI='
+ config['keystore'] = os.path.join(self.basedir, 'keystore.jks')
+ fdroidserver.common.config = config
+ fdroidserver.signindex.config = config
+
+ testdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir)
+ unsigned = os.path.join(testdir, 'urzip-release-unsigned.apk')
+ signed = os.path.join(testdir, 'urzip-release.apk')
+
+ self.assertFalse(fdroidserver.common.verify_apk_signature(unsigned))
+
+ shutil.copy(os.path.join(self.basedir, 'urzip-release-unsigned.apk'), testdir)
+ fdroidserver.common.sign_apk(unsigned, signed, config['keyalias'])
+ self.assertTrue(os.path.isfile(signed))
+ self.assertFalse(os.path.isfile(unsigned))
+ self.assertTrue(fdroidserver.common.verify_apk_signature(signed))
+
def test_get_api_id_aapt(self):
config = dict()
with self.assertRaises(FDroidException):
fdroidserver.common.get_apk_id_aapt('nope')
+ def test_get_minSdkVersion_aapt(self):
+
+ config = dict()
+ fdroidserver.common.fill_config_defaults(config)
+ fdroidserver.common.config = config
+ self._set_build_tools()
+ config['aapt'] = fdroidserver.common.find_sdk_tools_cmd('aapt')
+
+ minSdkVersion = fdroidserver.common.get_minSdkVersion_aapt('bad-unicode-πÇÇ现代通用字-български-عربي1.apk')
+ self.assertEqual(4, minSdkVersion)
+ minSdkVersion = fdroidserver.common.get_minSdkVersion_aapt('org.bitbucket.tickytacky.mirrormirror_1.apk')
+ self.assertEqual(14, minSdkVersion)
+ minSdkVersion = fdroidserver.common.get_minSdkVersion_aapt('org.bitbucket.tickytacky.mirrormirror_2.apk')
+ self.assertEqual(14, minSdkVersion)
+ minSdkVersion = fdroidserver.common.get_minSdkVersion_aapt('org.bitbucket.tickytacky.mirrormirror_3.apk')
+ self.assertEqual(14, minSdkVersion)
+ minSdkVersion = fdroidserver.common.get_minSdkVersion_aapt('org.bitbucket.tickytacky.mirrormirror_4.apk')
+ self.assertEqual(14, minSdkVersion)
+ minSdkVersion = fdroidserver.common.get_minSdkVersion_aapt('org.dyndns.fules.ck_20.apk')
+ self.assertEqual(7, minSdkVersion)
+ minSdkVersion = fdroidserver.common.get_minSdkVersion_aapt('urzip.apk')
+ self.assertEqual(4, minSdkVersion)
+ minSdkVersion = fdroidserver.common.get_minSdkVersion_aapt('urzip-badcert.apk')
+ self.assertEqual(4, minSdkVersion)
+ minSdkVersion = fdroidserver.common.get_minSdkVersion_aapt('urzip-badsig.apk')
+ self.assertEqual(4, minSdkVersion)
+ minSdkVersion = fdroidserver.common.get_minSdkVersion_aapt('urzip-release.apk')
+ self.assertEqual(4, minSdkVersion)
+ minSdkVersion = fdroidserver.common.get_minSdkVersion_aapt('urzip-release-unsigned.apk')
+ self.assertEqual(4, minSdkVersion)
+ minSdkVersion = fdroidserver.common.get_minSdkVersion_aapt('repo/com.politedroid_3.apk')
+ self.assertEqual(3, minSdkVersion)
+ minSdkVersion = fdroidserver.common.get_minSdkVersion_aapt('repo/com.politedroid_4.apk')
+ self.assertEqual(3, minSdkVersion)
+ minSdkVersion = fdroidserver.common.get_minSdkVersion_aapt('repo/com.politedroid_5.apk')
+ self.assertEqual(3, minSdkVersion)
+ minSdkVersion = fdroidserver.common.get_minSdkVersion_aapt('repo/com.politedroid_6.apk')
+ self.assertEqual(14, minSdkVersion)
+ minSdkVersion = fdroidserver.common.get_minSdkVersion_aapt('repo/obb.main.oldversion_1444412523.apk')
+ self.assertEqual(4, minSdkVersion)
+ minSdkVersion = fdroidserver.common.get_minSdkVersion_aapt('repo/obb.mainpatch.current_1619_another-release-key.apk')
+ self.assertEqual(4, minSdkVersion)
+ minSdkVersion = fdroidserver.common.get_minSdkVersion_aapt('repo/obb.mainpatch.current_1619.apk')
+ self.assertEqual(4, minSdkVersion)
+ minSdkVersion = fdroidserver.common.get_minSdkVersion_aapt('repo/obb.main.twoversions_1101613.apk')
+ self.assertEqual(4, minSdkVersion)
+ minSdkVersion = fdroidserver.common.get_minSdkVersion_aapt('repo/obb.main.twoversions_1101615.apk')
+ self.assertEqual(4, minSdkVersion)
+ minSdkVersion = fdroidserver.common.get_minSdkVersion_aapt('repo/obb.main.twoversions_1101617.apk')
+ self.assertEqual(4, minSdkVersion)
+ minSdkVersion = fdroidserver.common.get_minSdkVersion_aapt('repo/urzip-; Рахма́нинов, [rɐxˈmanʲɪnəf] سيرجي_رخمانينوف 谢尔盖·.apk')
+
+ with self.assertRaises(FDroidException):
+ fdroidserver.common.get_minSdkVersion_aapt('nope')
+
def test_apk_release_name(self):
appid, vercode, sigfp = fdroidserver.common.apk_parse_release_filename('com.serwylo.lexica_905.apk')
self.assertEqual(appid, 'com.serwylo.lexica')