2017-03-29 23:33:09 +02:00
|
|
|
#!/usr/bin/env python3
|
2017-04-03 19:19:37 +02:00
|
|
|
|
2020-10-01 10:02:05 +02:00
|
|
|
import datetime
|
2017-04-03 19:19:37 +02:00
|
|
|
import inspect
|
2017-11-30 10:14:38 +01:00
|
|
|
import logging
|
2017-03-29 23:33:09 +02:00
|
|
|
import optparse
|
|
|
|
import os
|
2017-04-03 19:19:37 +02:00
|
|
|
import sys
|
2017-03-29 23:33:09 +02:00
|
|
|
import unittest
|
|
|
|
import zipfile
|
2017-05-02 17:05:48 +02:00
|
|
|
from unittest.mock import patch
|
|
|
|
import requests
|
2017-09-20 00:16:13 +02:00
|
|
|
import tempfile
|
|
|
|
import json
|
|
|
|
import shutil
|
2017-03-29 23:33:09 +02:00
|
|
|
|
2017-04-03 19:19:37 +02:00
|
|
|
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)
|
|
|
|
|
2017-03-29 23:33:09 +02:00
|
|
|
import fdroidserver.common
|
|
|
|
import fdroidserver.index
|
|
|
|
import fdroidserver.signindex
|
2017-09-20 00:16:13 +02:00
|
|
|
import fdroidserver.publish
|
|
|
|
from testcommon import TmpCwd
|
2017-03-29 23:33:09 +02:00
|
|
|
|
|
|
|
|
2017-05-02 17:05:48 +02:00
|
|
|
GP_FINGERPRINT = 'B7C2EEFD8DAC7806AF67DFCD92EB18126BC08312A7F2D6F3862E46013C7A6135'
|
|
|
|
|
|
|
|
|
2020-10-01 10:02:05 +02:00
|
|
|
class Options:
|
|
|
|
nosign = True
|
|
|
|
pretty = False
|
|
|
|
verbose = False
|
|
|
|
|
|
|
|
|
2017-03-29 23:33:09 +02:00
|
|
|
class IndexTest(unittest.TestCase):
|
|
|
|
|
|
|
|
def setUp(self):
|
2017-11-30 10:14:38 +01:00
|
|
|
logging.basicConfig(level=logging.DEBUG)
|
|
|
|
self.basedir = os.path.join(localmodule, 'tests')
|
2020-10-01 10:02:05 +02:00
|
|
|
os.chmod(os.path.join(self.basedir, 'config.py'), 0o600)
|
2017-11-30 10:14:38 +01:00
|
|
|
self.tmpdir = os.path.abspath(os.path.join(self.basedir, '..', '.testfiles'))
|
|
|
|
if not os.path.exists(self.tmpdir):
|
|
|
|
os.makedirs(self.tmpdir)
|
|
|
|
os.chdir(self.basedir)
|
|
|
|
|
2017-03-29 23:33:09 +02:00
|
|
|
fdroidserver.common.config = None
|
2020-10-01 10:02:05 +02:00
|
|
|
fdroidserver.common.options = Options
|
2017-03-29 23:33:09 +02:00
|
|
|
config = fdroidserver.common.read_config(fdroidserver.common.options)
|
|
|
|
config['jarsigner'] = fdroidserver.common.find_sdk_tools_cmd('jarsigner')
|
|
|
|
fdroidserver.common.config = config
|
|
|
|
fdroidserver.signindex.config = config
|
|
|
|
|
|
|
|
def test_get_public_key_from_jar_succeeds(self):
|
2017-11-30 10:14:38 +01:00
|
|
|
source_dir = os.path.join(self.basedir, 'signindex')
|
2017-03-29 23:33:09 +02:00
|
|
|
for f in ('testy.jar', 'guardianproject.jar'):
|
|
|
|
testfile = os.path.join(source_dir, f)
|
|
|
|
jar = zipfile.ZipFile(testfile)
|
|
|
|
_, fingerprint = fdroidserver.index.get_public_key_from_jar(jar)
|
|
|
|
# comparing fingerprints should be sufficient
|
|
|
|
if f == 'testy.jar':
|
2018-05-29 13:51:47 +02:00
|
|
|
self.assertEqual(fingerprint,
|
|
|
|
'818E469465F96B704E27BE2FEE4C63AB'
|
|
|
|
+ '9F83DDF30E7A34C7371A4728D83B0BC1')
|
2017-03-29 23:33:09 +02:00
|
|
|
if f == 'guardianproject.jar':
|
2017-05-02 17:05:48 +02:00
|
|
|
self.assertTrue(fingerprint == GP_FINGERPRINT)
|
2017-03-29 23:33:09 +02:00
|
|
|
|
|
|
|
def test_get_public_key_from_jar_fails(self):
|
2017-11-30 10:14:38 +01:00
|
|
|
source_dir = os.path.join(self.basedir, 'signindex')
|
2017-03-29 23:33:09 +02:00
|
|
|
testfile = os.path.join(source_dir, 'unsigned.jar')
|
|
|
|
jar = zipfile.ZipFile(testfile)
|
|
|
|
with self.assertRaises(fdroidserver.index.VerificationException):
|
|
|
|
fdroidserver.index.get_public_key_from_jar(jar)
|
|
|
|
|
|
|
|
def test_download_repo_index_no_fingerprint(self):
|
|
|
|
with self.assertRaises(fdroidserver.index.VerificationException):
|
|
|
|
fdroidserver.index.download_repo_index("http://example.org")
|
|
|
|
|
|
|
|
def test_download_repo_index_no_jar(self):
|
2020-02-05 20:28:48 +01:00
|
|
|
with self.assertRaises(requests.exceptions.RequestException):
|
2017-03-29 23:33:09 +02:00
|
|
|
fdroidserver.index.download_repo_index("http://example.org?fingerprint=nope")
|
|
|
|
|
2017-05-02 17:05:48 +02:00
|
|
|
@patch('requests.head')
|
|
|
|
def test_download_repo_index_same_etag(self, head):
|
|
|
|
url = 'http://example.org?fingerprint=test'
|
|
|
|
etag = '"4de5-54d840ce95cb9"'
|
|
|
|
|
|
|
|
head.return_value.headers = {'ETag': etag}
|
|
|
|
index, new_etag = fdroidserver.index.download_repo_index(url, etag=etag)
|
|
|
|
|
|
|
|
self.assertIsNone(index)
|
|
|
|
self.assertEqual(etag, new_etag)
|
|
|
|
|
|
|
|
@patch('requests.get')
|
|
|
|
@patch('requests.head')
|
|
|
|
def test_download_repo_index_new_etag(self, head, get):
|
|
|
|
url = 'http://example.org?fingerprint=' + GP_FINGERPRINT
|
|
|
|
etag = '"4de5-54d840ce95cb9"'
|
|
|
|
|
|
|
|
# fake HTTP answers
|
|
|
|
head.return_value.headers = {'ETag': 'new_etag'}
|
|
|
|
get.return_value.headers = {'ETag': 'new_etag'}
|
|
|
|
get.return_value.status_code = 200
|
2018-08-14 10:08:03 +02:00
|
|
|
testfile = os.path.join('signindex', 'guardianproject-v1.jar')
|
2017-05-02 17:05:48 +02:00
|
|
|
with open(testfile, 'rb') as file:
|
|
|
|
get.return_value.content = file.read()
|
|
|
|
|
|
|
|
index, new_etag = fdroidserver.index.download_repo_index(url, etag=etag)
|
|
|
|
|
|
|
|
# assert that the index was retrieved properly
|
|
|
|
self.assertEqual('Guardian Project Official Releases', index['repo']['name'])
|
|
|
|
self.assertEqual(GP_FINGERPRINT, index['repo']['fingerprint'])
|
|
|
|
self.assertTrue(len(index['repo']['pubkey']) > 500)
|
|
|
|
self.assertEqual(10, len(index['apps']))
|
|
|
|
self.assertEqual(10, len(index['packages']))
|
|
|
|
self.assertEqual('new_etag', new_etag)
|
2017-03-29 23:33:09 +02:00
|
|
|
|
2017-09-20 00:16:13 +02:00
|
|
|
def test_v1_sort_packages(self):
|
|
|
|
|
|
|
|
i = [{'packageName': 'org.smssecure.smssecure',
|
|
|
|
'apkName': 'org.smssecure.smssecure_134.apk',
|
|
|
|
'signer': 'b33a601a9da97c82e6eb121eb6b90adab561f396602ec4dc8b0019fb587e2af6',
|
|
|
|
'versionCode': 134},
|
|
|
|
{'packageName': 'org.smssecure.smssecure',
|
|
|
|
'apkName': 'org.smssecure.smssecure_134_b30bb97.apk',
|
|
|
|
'signer': 'b30bb971af0d134866e158ec748fcd553df97c150f58b0a963190bbafbeb0868',
|
|
|
|
'versionCode': 134},
|
|
|
|
{'packageName': 'b075b32b4ef1e8a869e00edb136bd48e34a0382b85ced8628f164d1199584e4e'},
|
|
|
|
{'packageName': '43af70d1aca437c2f9974c4634cc5abe45bdc4d5d71529ac4e553488d3bb3ff6'},
|
|
|
|
{'packageName': 'org.smssecure.smssecure',
|
|
|
|
'apkName': 'org.smssecure.smssecure_135_b30bb97.apk',
|
|
|
|
'signer': 'b30bb971af0d134866e158ec748fcd553df97c150f58b0a963190bbafbeb0868',
|
|
|
|
'versionCode': 135},
|
|
|
|
{'packageName': 'org.smssecure.smssecure',
|
|
|
|
'apkName': 'org.smssecure.smssecure_135.apk',
|
|
|
|
'signer': 'b33a601a9da97c82e6eb121eb6b90adab561f396602ec4dc8b0019fb587e2af6',
|
|
|
|
'versionCode': 135},
|
|
|
|
{'packageName': 'org.smssecure.smssecure',
|
|
|
|
'apkName': 'org.smssecure.smssecure_133.apk',
|
|
|
|
'signer': 'b33a601a9da97c82e6eb121eb6b90adab561f396602ec4dc8b0019fb587e2af6',
|
|
|
|
'versionCode': 133},
|
|
|
|
{'packageName': 'org.smssecure.smssecure',
|
|
|
|
'apkName': 'smssecure-weird-version.apk',
|
|
|
|
'signer': '99ff99ff99ff99ff99ff99ff99ff99ff99ff99ff99ff99ff99ff99ff99ff99ff',
|
|
|
|
'versionCode': 133},
|
|
|
|
{'packageName': 'org.smssecure.smssecure',
|
|
|
|
'apkName': 'smssecure-custom.apk',
|
|
|
|
'signer': '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
|
|
|
|
'versionCode': 133},
|
|
|
|
{'packageName': 'org.smssecure.smssecure',
|
|
|
|
'apkName': 'smssecure-new-custom.apk',
|
|
|
|
'signer': '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
|
|
|
|
'versionCode': 135}]
|
|
|
|
|
|
|
|
o = [{'packageName': '43af70d1aca437c2f9974c4634cc5abe45bdc4d5d71529ac4e553488d3bb3ff6'},
|
|
|
|
{'packageName': 'b075b32b4ef1e8a869e00edb136bd48e34a0382b85ced8628f164d1199584e4e'},
|
|
|
|
# app test data
|
|
|
|
# # packages with reproducible developer signature
|
|
|
|
{'packageName': 'org.smssecure.smssecure',
|
|
|
|
'apkName': 'org.smssecure.smssecure_135_b30bb97.apk',
|
|
|
|
'signer': 'b30bb971af0d134866e158ec748fcd553df97c150f58b0a963190bbafbeb0868',
|
|
|
|
'versionCode': 135},
|
|
|
|
{'packageName': 'org.smssecure.smssecure',
|
|
|
|
'apkName': 'org.smssecure.smssecure_134_b30bb97.apk',
|
|
|
|
'signer': 'b30bb971af0d134866e158ec748fcd553df97c150f58b0a963190bbafbeb0868',
|
|
|
|
'versionCode': 134},
|
|
|
|
# # packages build and signed by fdroid
|
|
|
|
{'packageName': 'org.smssecure.smssecure',
|
|
|
|
'apkName': 'org.smssecure.smssecure_135.apk',
|
|
|
|
'signer': 'b33a601a9da97c82e6eb121eb6b90adab561f396602ec4dc8b0019fb587e2af6',
|
|
|
|
'versionCode': 135},
|
|
|
|
{'packageName': 'org.smssecure.smssecure',
|
|
|
|
'apkName': 'org.smssecure.smssecure_134.apk',
|
|
|
|
'signer': 'b33a601a9da97c82e6eb121eb6b90adab561f396602ec4dc8b0019fb587e2af6',
|
|
|
|
'versionCode': 134},
|
|
|
|
{'packageName': 'org.smssecure.smssecure',
|
|
|
|
'apkName': 'org.smssecure.smssecure_133.apk',
|
|
|
|
'signer': 'b33a601a9da97c82e6eb121eb6b90adab561f396602ec4dc8b0019fb587e2af6',
|
|
|
|
'versionCode': 133},
|
|
|
|
# # packages signed with unkown keys
|
|
|
|
{'packageName': 'org.smssecure.smssecure',
|
|
|
|
'apkName': 'smssecure-new-custom.apk',
|
|
|
|
'signer': '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
|
|
|
|
'versionCode': 135},
|
|
|
|
{'packageName': 'org.smssecure.smssecure',
|
|
|
|
'apkName': 'smssecure-custom.apk',
|
|
|
|
'signer': '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
|
|
|
|
'versionCode': 133},
|
|
|
|
{'packageName': 'org.smssecure.smssecure',
|
|
|
|
'apkName': 'smssecure-weird-version.apk',
|
|
|
|
'signer': '99ff99ff99ff99ff99ff99ff99ff99ff99ff99ff99ff99ff99ff99ff99ff99ff',
|
|
|
|
'versionCode': 133}]
|
|
|
|
|
|
|
|
fdroidserver.common.config = {}
|
|
|
|
fdroidserver.common.fill_config_defaults(fdroidserver.common.config)
|
|
|
|
fdroidserver.publish.config = fdroidserver.common.config
|
|
|
|
fdroidserver.publish.config['keystorepass'] = '123456'
|
|
|
|
fdroidserver.publish.config['keypass'] = '123456'
|
|
|
|
fdroidserver.publish.config['keystore'] = os.path.join(os.getcwd(),
|
|
|
|
'dummy-keystore.jks')
|
|
|
|
fdroidserver.publish.config['repo_keyalias'] = 'repokey'
|
|
|
|
|
|
|
|
testsmetadir = os.path.join(os.getcwd(), 'metadata')
|
|
|
|
with tempfile.TemporaryDirectory() as tmpdir, TmpCwd(tmpdir):
|
|
|
|
shutil.copytree(testsmetadir, 'metadata')
|
|
|
|
sigkeyfps = {
|
|
|
|
"org.smssecure.smssecure": {
|
|
|
|
"signer": "b33a601a9da97c82e6eb121eb6b90adab561f396602ec4dc8b0019fb587e2af6"
|
|
|
|
}
|
|
|
|
}
|
2017-09-05 15:17:22 +02:00
|
|
|
os.makedirs('stats')
|
|
|
|
jarfile = 'stats/publishsigkeys.jar'
|
2017-09-20 00:16:13 +02:00
|
|
|
with zipfile.ZipFile(jarfile, 'w', zipfile.ZIP_DEFLATED) as jar:
|
|
|
|
jar.writestr('publishsigkeys.json', json.dumps(sigkeyfps))
|
|
|
|
fdroidserver.publish.sign_sig_key_fingerprint_list(jarfile)
|
|
|
|
with open('config.py', 'w'):
|
|
|
|
pass
|
|
|
|
|
|
|
|
fdroidserver.index.v1_sort_packages(
|
2018-05-14 14:07:40 +02:00
|
|
|
i, fdroidserver.common.load_stats_fdroid_signing_key_fingerprints())
|
2017-09-20 00:16:13 +02:00
|
|
|
self.maxDiff = None
|
|
|
|
self.assertEqual(json.dumps(i, indent=2), json.dumps(o, indent=2))
|
|
|
|
|
2020-10-01 10:02:05 +02:00
|
|
|
def test_make_v0(self):
|
|
|
|
tmptestsdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name,
|
|
|
|
dir=self.tmpdir)
|
|
|
|
os.chdir(tmptestsdir)
|
|
|
|
os.mkdir('repo')
|
|
|
|
repo_icons_dir = os.path.join('repo', 'icons')
|
|
|
|
self.assertFalse(os.path.isdir(repo_icons_dir))
|
|
|
|
repodict = {
|
|
|
|
'address': 'https://example.com/fdroid/repo',
|
|
|
|
'description': 'This is just a test',
|
|
|
|
'icon': 'blahblah',
|
|
|
|
'name': 'test',
|
|
|
|
'timestamp': datetime.datetime.now(),
|
|
|
|
'version': 12,
|
|
|
|
}
|
|
|
|
requestsdict = {'install': [], 'uninstall': []}
|
|
|
|
fdroidserver.common.config['repo_pubkey'] = 'ffffffffffffffffffffffffffffffffff'
|
|
|
|
fdroidserver.index.make_v0({}, [], 'repo', repodict, requestsdict, [])
|
|
|
|
self.assertTrue(os.path.isdir(repo_icons_dir))
|
|
|
|
self.assertTrue(os.path.exists(os.path.join(repo_icons_dir,
|
|
|
|
fdroidserver.common.default_config['repo_icon'])))
|
|
|
|
self.assertTrue(os.path.exists(os.path.join('repo', 'index.xml')))
|
|
|
|
|
2017-03-29 23:33:09 +02:00
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2018-08-14 10:08:03 +02:00
|
|
|
os.chdir(os.path.dirname(__file__))
|
|
|
|
|
2017-03-29 23:33:09 +02:00
|
|
|
parser = optparse.OptionParser()
|
|
|
|
parser.add_option("-v", "--verbose", action="store_true", default=False,
|
|
|
|
help="Spew out even more information than normal")
|
2020-10-01 10:02:05 +02:00
|
|
|
(options, args) = parser.parse_args()
|
|
|
|
Options.verbose = options.verbose
|
2017-03-29 23:33:09 +02:00
|
|
|
|
|
|
|
newSuite = unittest.TestSuite()
|
|
|
|
newSuite.addTest(unittest.makeSuite(IndexTest))
|
2017-10-20 11:51:59 +02:00
|
|
|
unittest.main(failfast=False)
|