mirror of
https://gitlab.com/fdroid/fdroidserver.git
synced 2024-11-14 19:10:11 +01:00
223c793201
Google has their own utility for verifying APK signatures on a desktop machine since Java's jarsigner is bad for the task. For example, it acts as if an unsigned APK validates. And to check whether an APK is unsigned using jarsigner is difficult. apksigner also does the v2 signatures, so it will have to be used eventually anyway. It is already in Debian/stretch and can be available in jessie-backports if need be. https://android.googlesource.com/platform/tools/apksig https://packages.debian.org/apksigner
243 lines
10 KiB
Python
Executable File
243 lines
10 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
# http://www.drdobbs.com/testing/unit-testing-with-python/240165163
|
|
|
|
import inspect
|
|
import optparse
|
|
import os
|
|
import re
|
|
import shutil
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from zipfile import ZipFile
|
|
|
|
localmodule = os.path.realpath(
|
|
os.path.join(os.path.dirname(inspect.getfile(inspect.currentframe())), '..'))
|
|
print('localmodule: ' + localmodule)
|
|
if localmodule not in sys.path:
|
|
sys.path.insert(0, localmodule)
|
|
|
|
import fdroidserver.common
|
|
import fdroidserver.metadata
|
|
|
|
|
|
class CommonTest(unittest.TestCase):
|
|
'''fdroidserver/common.py'''
|
|
|
|
def _set_build_tools(self):
|
|
build_tools = os.path.join(fdroidserver.common.config['sdk_path'], 'build-tools')
|
|
if os.path.exists(build_tools):
|
|
fdroidserver.common.config['build_tools'] = ''
|
|
for f in sorted(os.listdir(build_tools), reverse=True):
|
|
versioned = os.path.join(build_tools, f)
|
|
if os.path.isdir(versioned) \
|
|
and os.path.isfile(os.path.join(versioned, 'aapt')):
|
|
fdroidserver.common.config['build_tools'] = versioned
|
|
break
|
|
return True
|
|
else:
|
|
print('no build-tools found: ' + build_tools)
|
|
return False
|
|
|
|
def _find_all(self):
|
|
for cmd in ('aapt', 'adb', 'android', 'zipalign'):
|
|
path = fdroidserver.common.find_sdk_tools_cmd(cmd)
|
|
if path is not None:
|
|
self.assertTrue(os.path.exists(path))
|
|
self.assertTrue(os.path.isfile(path))
|
|
|
|
def test_find_sdk_tools_cmd(self):
|
|
fdroidserver.common.config = dict()
|
|
# TODO add this once everything works without sdk_path set in config
|
|
# self._find_all()
|
|
sdk_path = os.getenv('ANDROID_HOME')
|
|
if os.path.exists(sdk_path):
|
|
fdroidserver.common.config['sdk_path'] = sdk_path
|
|
if os.path.exists('/usr/bin/aapt'):
|
|
# this test only works when /usr/bin/aapt is installed
|
|
self._find_all()
|
|
build_tools = os.path.join(sdk_path, 'build-tools')
|
|
if self._set_build_tools():
|
|
self._find_all()
|
|
else:
|
|
print('no build-tools found: ' + build_tools)
|
|
|
|
def testIsApkDebuggable(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')
|
|
# these are set debuggable
|
|
testfiles = []
|
|
testfiles.append(os.path.join(os.path.dirname(__file__), 'urzip.apk'))
|
|
testfiles.append(os.path.join(os.path.dirname(__file__), 'urzip-badsig.apk'))
|
|
testfiles.append(os.path.join(os.path.dirname(__file__), 'urzip-badcert.apk'))
|
|
for apkfile in testfiles:
|
|
debuggable = fdroidserver.common.isApkAndDebuggable(apkfile, config)
|
|
self.assertTrue(debuggable,
|
|
"debuggable APK state was not properly parsed!")
|
|
# these are set NOT debuggable
|
|
testfiles = []
|
|
testfiles.append(os.path.join(os.path.dirname(__file__), 'urzip-release.apk'))
|
|
testfiles.append(os.path.join(os.path.dirname(__file__), 'urzip-release-unsigned.apk'))
|
|
for apkfile in testfiles:
|
|
debuggable = fdroidserver.common.isApkAndDebuggable(apkfile, config)
|
|
self.assertFalse(debuggable,
|
|
"debuggable APK state was not properly parsed!")
|
|
|
|
def testPackageNameValidity(self):
|
|
for name in ["org.fdroid.fdroid",
|
|
"org.f_droid.fdr0ID"]:
|
|
self.assertTrue(fdroidserver.common.is_valid_package_name(name),
|
|
"{0} should be a valid package name".format(name))
|
|
for name in ["0rg.fdroid.fdroid",
|
|
".f_droid.fdr0ID",
|
|
"org.fdroid/fdroid",
|
|
"/org.fdroid.fdroid"]:
|
|
self.assertFalse(fdroidserver.common.is_valid_package_name(name),
|
|
"{0} should not be a valid package name".format(name))
|
|
|
|
def test_prepare_sources(self):
|
|
testint = 99999999
|
|
teststr = 'FAKE_STR_FOR_TESTING'
|
|
|
|
tmpdir = os.path.join(os.path.dirname(__file__), '..', '.testfiles')
|
|
if not os.path.exists(tmpdir):
|
|
os.makedirs(tmpdir)
|
|
tmptestsdir = tempfile.mkdtemp(prefix='test_prepare_sources', dir=tmpdir)
|
|
shutil.copytree(os.path.join(os.path.dirname(__file__), 'source-files'),
|
|
os.path.join(tmptestsdir, 'source-files'))
|
|
|
|
testdir = os.path.join(tmptestsdir, 'source-files', 'fdroid', 'fdroidclient')
|
|
|
|
config = dict()
|
|
config['sdk_path'] = os.getenv('ANDROID_HOME')
|
|
config['ndk_paths'] = {'r10d': os.getenv('ANDROID_NDK_HOME')}
|
|
config['build_tools'] = 'FAKE_BUILD_TOOLS_VERSION'
|
|
fdroidserver.common.config = config
|
|
app = fdroidserver.metadata.App()
|
|
app.id = 'org.fdroid.froid'
|
|
build = fdroidserver.metadata.Build()
|
|
build.commit = 'master'
|
|
build.forceversion = True
|
|
build.forcevercode = True
|
|
build.gradle = ['yes']
|
|
build.target = 'android-' + str(testint)
|
|
build.versionName = teststr
|
|
build.versionCode = testint
|
|
|
|
class FakeVcs():
|
|
# no need to change to the correct commit here
|
|
def gotorevision(self, rev, refresh=True):
|
|
pass
|
|
|
|
# no srclib info needed, but it could be added...
|
|
def getsrclib(self):
|
|
return None
|
|
|
|
fdroidserver.common.prepare_source(FakeVcs(), app, build, testdir, testdir, testdir)
|
|
|
|
with open(os.path.join(testdir, 'build.gradle'), 'r') as f:
|
|
filedata = f.read()
|
|
self.assertIsNotNone(re.search("\s+compileSdkVersion %s\s+" % testint, filedata))
|
|
|
|
with open(os.path.join(testdir, 'AndroidManifest.xml')) as f:
|
|
filedata = f.read()
|
|
self.assertIsNone(re.search('android:debuggable', filedata))
|
|
self.assertIsNotNone(re.search('android:versionName="%s"' % build.versionName, filedata))
|
|
self.assertIsNotNone(re.search('android:versionCode="%s"' % build.versionCode, filedata))
|
|
|
|
def test_fdroid_popen_stderr_redirect(self):
|
|
commands = ['sh', '-c', 'echo stdout message && echo stderr message 1>&2']
|
|
|
|
p = fdroidserver.common.FDroidPopen(commands)
|
|
self.assertEqual(p.output, 'stdout message\nstderr message\n')
|
|
|
|
p = fdroidserver.common.FDroidPopen(commands, stderr_to_stdout=False)
|
|
self.assertEqual(p.output, 'stdout message\n')
|
|
|
|
def test_signjar(self):
|
|
fdroidserver.common.config = None
|
|
config = fdroidserver.common.read_config(fdroidserver.common.options)
|
|
config['jarsigner'] = fdroidserver.common.find_sdk_tools_cmd('jarsigner')
|
|
fdroidserver.common.config = config
|
|
|
|
basedir = os.path.dirname(__file__)
|
|
tmpdir = os.path.join(basedir, '..', '.testfiles')
|
|
if not os.path.exists(tmpdir):
|
|
os.makedirs(tmpdir)
|
|
sourcedir = os.path.join(basedir, 'signindex')
|
|
testsdir = tempfile.mkdtemp(prefix='test_signjar', dir=tmpdir)
|
|
for f in ('testy.jar', 'guardianproject.jar',):
|
|
sourcefile = os.path.join(sourcedir, f)
|
|
testfile = os.path.join(testsdir, f)
|
|
shutil.copy(sourcefile, testsdir)
|
|
fdroidserver.common.signjar(testfile)
|
|
# these should be resigned, and therefore different
|
|
self.assertNotEqual(open(sourcefile, 'rb').read(), open(testfile, 'rb').read())
|
|
|
|
def test_verify_apk_signature(self):
|
|
fdroidserver.common.config = None
|
|
config = fdroidserver.common.read_config(fdroidserver.common.options)
|
|
config['jarsigner'] = fdroidserver.common.find_sdk_tools_cmd('jarsigner')
|
|
fdroidserver.common.config = config
|
|
|
|
self.assertTrue(fdroidserver.common.verify_apk_signature('urzip.apk'))
|
|
self.assertFalse(fdroidserver.common.verify_apk_signature('urzip-badcert.apk'))
|
|
self.assertFalse(fdroidserver.common.verify_apk_signature('urzip-badsig.apk'))
|
|
self.assertTrue(fdroidserver.common.verify_apk_signature('urzip-release.apk'))
|
|
self.assertFalse(fdroidserver.common.verify_apk_signature('urzip-release-unsigned.apk'))
|
|
|
|
def test_verify_apks(self):
|
|
fdroidserver.common.config = None
|
|
config = fdroidserver.common.read_config(fdroidserver.common.options)
|
|
config['jarsigner'] = fdroidserver.common.find_sdk_tools_cmd('jarsigner')
|
|
fdroidserver.common.config = config
|
|
|
|
basedir = os.path.dirname(__file__)
|
|
sourceapk = os.path.join(basedir, 'urzip.apk')
|
|
|
|
tmpdir = os.path.join(basedir, '..', '.testfiles')
|
|
if not os.path.exists(tmpdir):
|
|
os.makedirs(tmpdir)
|
|
testdir = tempfile.mkdtemp(prefix='test_verify_apks', dir=tmpdir)
|
|
print('testdir', testdir)
|
|
|
|
copyapk = os.path.join(testdir, 'urzip-copy.apk')
|
|
shutil.copy(sourceapk, copyapk)
|
|
self.assertTrue(fdroidserver.common.verify_apk_signature(copyapk))
|
|
self.assertIsNone(fdroidserver.common.verify_apks(sourceapk, copyapk, tmpdir))
|
|
|
|
unsignedapk = os.path.join(testdir, 'urzip-unsigned.apk')
|
|
with ZipFile(sourceapk, 'r') as apk:
|
|
with ZipFile(unsignedapk, 'w') as testapk:
|
|
for info in apk.infolist():
|
|
if not info.filename.startswith('META-INF/'):
|
|
testapk.writestr(info, apk.read(info.filename))
|
|
self.assertIsNone(fdroidserver.common.verify_apks(sourceapk, unsignedapk, tmpdir))
|
|
|
|
twosigapk = os.path.join(testdir, 'urzip-twosig.apk')
|
|
otherapk = ZipFile(os.path.join(basedir, 'urzip-release.apk'), 'r')
|
|
with ZipFile(sourceapk, 'r') as apk:
|
|
with ZipFile(twosigapk, 'w') as testapk:
|
|
for info in apk.infolist():
|
|
testapk.writestr(info, apk.read(info.filename))
|
|
if info.filename.startswith('META-INF/'):
|
|
testapk.writestr(info, otherapk.read(info.filename))
|
|
otherapk.close()
|
|
self.assertFalse(fdroidserver.common.verify_apk_signature(twosigapk))
|
|
self.assertIsNone(fdroidserver.common.verify_apks(sourceapk, twosigapk, tmpdir))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
parser = optparse.OptionParser()
|
|
parser.add_option("-v", "--verbose", action="store_true", default=False,
|
|
help="Spew out even more information than normal")
|
|
(fdroidserver.common.options, args) = parser.parse_args(['--verbose'])
|
|
|
|
newSuite = unittest.TestSuite()
|
|
newSuite.addTest(unittest.makeSuite(CommonTest))
|
|
unittest.main()
|