Merge branch 'androguard-modernization' into 'master'

port to androguard >= 4 and drop support for older than 3.3.5

See merge request fdroid/fdroidserver!1462
This commit is contained in:
Hans-Christoph Steiner 2024-04-25 11:09:27 +00:00
commit 32ef77ecb6
7 changed files with 73 additions and 46 deletions

View file

@ -2629,29 +2629,46 @@ def get_file_extension(filename):
return os.path.splitext(filename)[1].lower()[1:] return os.path.splitext(filename)[1].lower()[1:]
def use_androguard(): def _androguard_logging_level(level=logging.ERROR):
"""Report if androguard is available, and config its debug logging.""" """Tames androguard's default debug output.
There should be no debug output when the functions are being used
via the API. Otherwise, the output is controlled by the --verbose
flag.
To get coverage across the full range of androguard >= 3.3.5, this
includes all known logger names that are relevant. So some of
these names might not be present in the version of androguard
currently in use.
"""
if options and options.verbose:
level = logging.WARNING
for name in (
'androguard.apk',
'androguard.axml',
'androguard.core.api_specific_resources',
'androguard.core.apk',
'androguard.core.axml',
):
logging.getLogger(name).setLevel(level)
# some parts of androguard 4.x use loguru instead of logging
try: try:
import androguard from loguru import logger
if use_androguard.show_path: logger.remove()
logging.debug(_('Using androguard from "{path}"').format(path=androguard.__file__))
use_androguard.show_path = False
if options and options.verbose:
logging.getLogger("androguard.axml").setLevel(logging.INFO)
logging.getLogger("androguard.core.api_specific_resources").setLevel(logging.ERROR)
return True
except ImportError: except ImportError:
return False pass
use_androguard.show_path = True # type: ignore def get_androguard_APK(apkfile):
def _get_androguard_APK(apkfile):
try: try:
# these were moved in androguard 4.0
from androguard.core.apk import APK
except ImportError:
from androguard.core.bytecodes.apk import APK from androguard.core.bytecodes.apk import APK
except ImportError as exc: _androguard_logging_level()
raise FDroidException("androguard library is not installed") from exc
return APK(apkfile) return APK(apkfile)
@ -2693,7 +2710,13 @@ def is_debuggable_or_testOnly(apkfile):
""" """
if get_file_extension(apkfile) != 'apk': if get_file_extension(apkfile) != 'apk':
return False return False
from androguard.core.bytecodes.axml import AXMLParser, format_value, START_TAG try:
# these were moved in androguard 4.0
from androguard.core.axml import AXMLParser, format_value, START_TAG
except ImportError:
from androguard.core.bytecodes.axml import AXMLParser, format_value, START_TAG
_androguard_logging_level()
with ZipFile(apkfile) as apk: with ZipFile(apkfile) as apk:
with apk.open('AndroidManifest.xml') as manifest: with apk.open('AndroidManifest.xml') as manifest:
axml = AXMLParser(manifest.read()) axml = AXMLParser(manifest.read())
@ -2753,12 +2776,20 @@ def get_apk_id_androguard(apkfile):
versionName is set to a Android String Resource (e.g. an integer versionName is set to a Android String Resource (e.g. an integer
hex value that starts with @). hex value that starts with @).
This function is part of androguard as get_apkid(), so this
vendored and modified to return versionCode as an integer.
""" """
if not os.path.exists(apkfile): if not os.path.exists(apkfile):
raise FDroidException(_("Reading packageName/versionCode/versionName failed, APK invalid: '{apkfilename}'") raise FDroidException(_("Reading packageName/versionCode/versionName failed, APK invalid: '{apkfilename}'")
.format(apkfilename=apkfile)) .format(apkfilename=apkfile))
from androguard.core.bytecodes.axml import AXMLParser, format_value, START_TAG, END_TAG, TEXT, END_DOCUMENT try:
# these were moved in androguard 4.0
from androguard.core.axml import AXMLParser, format_value, START_TAG, END_TAG, TEXT, END_DOCUMENT
except ImportError:
from androguard.core.bytecodes.axml import AXMLParser, format_value, START_TAG, END_TAG, TEXT, END_DOCUMENT
_androguard_logging_level()
appid = None appid = None
versionCode = None versionCode = None
@ -2793,7 +2824,7 @@ def get_apk_id_androguard(apkfile):
.format(path=apkfile)) .format(path=apkfile))
if not versionName or versionName[0] == '@': if not versionName or versionName[0] == '@':
a = _get_androguard_APK(apkfile) a = get_androguard_APK(apkfile)
versionName = ensure_final_value(a.package, a.get_android_resources(), a.get_androidversion_name()) versionName = ensure_final_value(a.package, a.get_android_resources(), a.get_androidversion_name())
if not versionName: if not versionName:
versionName = '' # versionName is expected to always be a str versionName = '' # versionName is expected to always be a str
@ -3159,8 +3190,8 @@ def get_first_signer_certificate(apkpath):
elif len(cert_files) == 1: elif len(cert_files) == 1:
cert_encoded = get_certificate(apk.read(cert_files[0])) cert_encoded = get_certificate(apk.read(cert_files[0]))
if not cert_encoded and use_androguard(): if not cert_encoded:
apkobject = _get_androguard_APK(apkpath) apkobject = get_androguard_APK(apkpath)
certs = apkobject.get_certificates_der_v2() certs = apkobject.get_certificates_der_v2()
if len(certs) > 0: if len(certs) > 0:
logging.debug(_('Using APK Signature v2')) logging.debug(_('Using APK Signature v2'))

View file

@ -1722,8 +1722,7 @@ def _sanitize_sdk_version(value):
def scan_apk_androguard(apk, apkfile): def scan_apk_androguard(apk, apkfile):
try: try:
from androguard.core.bytecodes.apk import APK apkobject = common.get_androguard_APK(apkfile)
apkobject = APK(apkfile)
if apkobject.is_valid_APK(): if apkobject.is_valid_APK():
arsc = apkobject.get_android_resources() arsc = apkobject.get_android_resources()
else: else:
@ -1739,7 +1738,7 @@ def scan_apk_androguard(apk, apkfile):
logging.error(_("Failed to get APK information, skipping {path}") logging.error(_("Failed to get APK information, skipping {path}")
.format(path=apkfile)) .format(path=apkfile))
raise BuildException(_("Invalid APK")) raise BuildException(_("Invalid APK"))
except (FileNotFoundError, zipfile.BadZipFile) as e: except (FileNotFoundError, ValueError, zipfile.BadZipFile) as e:
logging.error(_("Could not open APK {path} for analysis: ").format(path=apkfile) logging.error(_("Could not open APK {path} for analysis: ").format(path=apkfile)
+ str(e)) + str(e))
raise BuildException(_("Invalid APK")) from e raise BuildException(_("Invalid APK")) from e
@ -1779,7 +1778,7 @@ def scan_apk_androguard(apk, apkfile):
if maxSdkVersion is not None: if maxSdkVersion is not None:
apk['maxSdkVersion'] = maxSdkVersion apk['maxSdkVersion'] = maxSdkVersion
icon_id_str = apkobject.get_element("application", "icon") icon_id_str = apkobject.get_attribute_value("application", "icon")
if icon_id_str: if icon_id_str:
try: try:
icon_id = int(icon_id_str.replace("@", "0x"), 16) icon_id = int(icon_id_str.replace("@", "0x"), 16)
@ -2581,7 +2580,6 @@ def main():
config = common.read_config(options) config = common.read_config(options)
common.setup_status_output(start_timestamp) common.setup_status_output(start_timestamp)
common.use_androguard()
if not (('jarsigner' in config or 'apksigner' in config) if not (('jarsigner' in config or 'apksigner' in config)
and 'keytool' in config): and 'keytool' in config):
raise FDroidException(_('Java JDK not found! Install in standard location or set java_paths!')) raise FDroidException(_('Java JDK not found! Install in standard location or set java_paths!'))

View file

@ -92,7 +92,7 @@ setup(
], ],
install_requires=[ install_requires=[
'appdirs', 'appdirs',
'androguard >= 3.1.0, != 3.3.0, != 3.3.1, != 3.3.2, <4', 'androguard >= 3.3.5',
'clint', 'clint',
'defusedxml', 'defusedxml',
'GitPython', 'GitPython',

View file

@ -49,8 +49,6 @@ class BuildTest(unittest.TestCase):
def setUp(self): def setUp(self):
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger('androguard.axml')
logger.setLevel(logging.INFO) # tame the axml debug messages
self.basedir = os.path.join(localmodule, 'tests') self.basedir = os.path.join(localmodule, 'tests')
os.chdir(self.basedir) os.chdir(self.basedir)
fdroidserver.common.config = None fdroidserver.common.config = None

View file

@ -902,7 +902,7 @@ class CommonTest(unittest.TestCase):
self.assertTrue(os.path.isfile(signed)) self.assertTrue(os.path.isfile(signed))
self.assertFalse(os.path.isfile(unsigned)) self.assertFalse(os.path.isfile(unsigned))
self.assertTrue(fdroidserver.common.verify_apk_signature(signed)) self.assertTrue(fdroidserver.common.verify_apk_signature(signed))
self.assertEqual('18', fdroidserver.common._get_androguard_APK(signed).get_min_sdk_version()) self.assertEqual('18', fdroidserver.common.get_androguard_APK(signed).get_min_sdk_version())
shutil.copy(os.path.join(self.basedir, 'minimal_targetsdk_30_unsigned.apk'), self.testdir) shutil.copy(os.path.join(self.basedir, 'minimal_targetsdk_30_unsigned.apk'), self.testdir)
unsigned = os.path.join(self.testdir, 'minimal_targetsdk_30_unsigned.apk') unsigned = os.path.join(self.testdir, 'minimal_targetsdk_30_unsigned.apk')
@ -915,7 +915,7 @@ class CommonTest(unittest.TestCase):
self.assertFalse(os.path.isfile(unsigned)) self.assertFalse(os.path.isfile(unsigned))
self.assertTrue(fdroidserver.common.verify_apk_signature(signed)) self.assertTrue(fdroidserver.common.verify_apk_signature(signed))
# verify it has a v2 signature # verify it has a v2 signature
self.assertTrue(fdroidserver.common._get_androguard_APK(signed).is_signed_v2()) self.assertTrue(fdroidserver.common.get_androguard_APK(signed).is_signed_v2())
shutil.copy(os.path.join(self.basedir, 'no_targetsdk_minsdk30_unsigned.apk'), self.testdir) shutil.copy(os.path.join(self.basedir, 'no_targetsdk_minsdk30_unsigned.apk'), self.testdir)
unsigned = os.path.join(self.testdir, 'no_targetsdk_minsdk30_unsigned.apk') unsigned = os.path.join(self.testdir, 'no_targetsdk_minsdk30_unsigned.apk')
@ -923,7 +923,7 @@ class CommonTest(unittest.TestCase):
fdroidserver.common.sign_apk(unsigned, signed, config['keyalias']) fdroidserver.common.sign_apk(unsigned, signed, config['keyalias'])
self.assertTrue(fdroidserver.common.verify_apk_signature(signed)) self.assertTrue(fdroidserver.common.verify_apk_signature(signed))
self.assertTrue(fdroidserver.common._get_androguard_APK(signed).is_signed_v2()) self.assertTrue(fdroidserver.common.get_androguard_APK(signed).is_signed_v2())
shutil.copy(os.path.join(self.basedir, 'no_targetsdk_minsdk1_unsigned.apk'), self.testdir) shutil.copy(os.path.join(self.basedir, 'no_targetsdk_minsdk1_unsigned.apk'), self.testdir)
unsigned = os.path.join(self.testdir, 'no_targetsdk_minsdk1_unsigned.apk') unsigned = os.path.join(self.testdir, 'no_targetsdk_minsdk1_unsigned.apk')
@ -1146,11 +1146,11 @@ class CommonTest(unittest.TestCase):
"""This is a sanity test that androguard isn't broken""" """This is a sanity test that androguard isn't broken"""
def get_minSdkVersion(apkfile): def get_minSdkVersion(apkfile):
apk = fdroidserver.common._get_androguard_APK(apkfile) apk = fdroidserver.common.get_androguard_APK(apkfile)
return fdroidserver.common.get_min_sdk_version(apk) return fdroidserver.common.get_min_sdk_version(apk)
def get_targetSdkVersion(apkfile): def get_targetSdkVersion(apkfile):
apk = fdroidserver.common._get_androguard_APK(apkfile) apk = fdroidserver.common.get_androguard_APK(apkfile)
return apk.get_effective_target_sdk_version() return apk.get_effective_target_sdk_version()
self.assertEqual(4, get_minSdkVersion('bad-unicode-πÇÇ现代通用字-български-عربي1.apk')) self.assertEqual(4, get_minSdkVersion('bad-unicode-πÇÇ现代通用字-български-عربي1.apk'))

View file

@ -8,7 +8,9 @@ echo_header() {
get_fdroid_apk_filename() { get_fdroid_apk_filename() {
if [ -z $aapt ]; then if [ -z $aapt ]; then
python3 -c "from androguard.core.bytecodes.apk import APK; a=APK('$1'); print(a.package+'_'+a.get_androidversion_code()+'.apk')" appid=$(androguard apkid "$1" | sed -En 's/ +"([a-z][^"]+)",$/\1/ip')
versionCode=$(androguard apkid "$1" | sed -En 's/ +"([0-9]+)",$/\1/p')
echo "${appid}_${versionCode}.apk"
else else
$aapt dump badging "$1" | sed -n "s,^package: name='\(.*\)' versionCode='\([0-9][0-9]*\)' .*,\1_\2.apk,p" $aapt dump badging "$1" | sed -n "s,^package: name='\(.*\)' versionCode='\([0-9][0-9]*\)' .*,\1_\2.apk,p"
fi fi

View file

@ -5,6 +5,7 @@
import copy import copy
import git import git
import glob import glob
import hashlib
import inspect import inspect
import json import json
import logging import logging
@ -20,11 +21,18 @@ import unittest
import yaml import yaml
import zipfile import zipfile
import textwrap import textwrap
from binascii import hexlify
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
from testcommon import TmpCwd, mkdtemp from testcommon import TmpCwd, mkdtemp
from unittest import mock from unittest import mock
try:
# these were moved in androguard 4.0
from androguard.core.apk import APK
except ImportError:
from androguard.core.bytecodes.apk import APK
try: try:
from yaml import CSafeLoader as SafeLoader from yaml import CSafeLoader as SafeLoader
except ImportError: except ImportError:
@ -74,9 +82,6 @@ class UpdateTest(unittest.TestCase):
def setUp(self): def setUp(self):
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
logging.getLogger('androguard.apk').setLevel(logging.WARNING)
logging.getLogger('androguard.axml').setLevel(logging.INFO)
logging.getLogger('androguard.core.api_specific_resources').setLevel(logging.INFO)
from PIL import PngImagePlugin from PIL import PngImagePlugin
logging.getLogger(PngImagePlugin.__name__).setLevel(logging.INFO) logging.getLogger(PngImagePlugin.__name__).setLevel(logging.INFO)
@ -581,13 +586,6 @@ class UpdateTest(unittest.TestCase):
self.assertEqual(good_fingerprint, sig, self.assertEqual(good_fingerprint, sig,
'python sig was: ' + str(sig)) 'python sig was: ' + str(sig))
# check that v1 and v2 have the same certificate # check that v1 and v2 have the same certificate
try:
import hashlib
from binascii import hexlify
from androguard.core.bytecodes.apk import APK
except ImportError:
print('WARNING: skipping rest of test since androguard is missing!')
return
apkobject = APK(apkpath) apkobject = APK(apkpath)
cert_encoded = apkobject.get_certificates_der_v2()[0] cert_encoded = apkobject.get_certificates_der_v2()[0]
self.assertEqual(good_fingerprint, sig, self.assertEqual(good_fingerprint, sig,