1
0
mirror of https://gitlab.com/fdroid/fdroidserver.git synced 2024-07-08 10:10:10 +02:00
fdroidserver/tests/common.TestCase
Hans-Christoph Steiner 223c793201 prefer apksigner if installed, jarsigner sucks
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
2017-03-22 10:51:12 +01:00

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()