mirror of
https://gitlab.com/fdroid/fdroidserver.git
synced 2024-11-10 17:30:11 +01:00
18f3acc32e
There is no longer any reason for these to be intertwined. This deliberately avoids touching some files as much as possible because they are super tangled and due to be replaced. Those files are: * fdroidserver/build.py * fdroidserver/update.py # Conflicts: # tests/testcommon.py # Conflicts: # fdroidserver/btlog.py # fdroidserver/import_subcommand.py
210 lines
7.6 KiB
Python
Executable File
210 lines
7.6 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
import inspect
|
|
import json
|
|
import logging
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
|
|
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)
|
|
|
|
from fdroidserver import apksigcopier, common, exception, signindex, update
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
|
|
|
|
class Options:
|
|
allow_disabled_algorithms = False
|
|
clean = False
|
|
delete_unknown = False
|
|
nosign = False
|
|
pretty = True
|
|
rename_apks = False
|
|
verbose = False
|
|
|
|
|
|
class SignindexTest(unittest.TestCase):
|
|
basedir = Path(__file__).resolve().parent
|
|
|
|
def setUp(self):
|
|
signindex.config = None
|
|
config = common.read_config()
|
|
config['jarsigner'] = common.find_sdk_tools_cmd('jarsigner')
|
|
config['verbose'] = True
|
|
config['keystore'] = str(self.basedir / 'keystore.jks')
|
|
config['repo_keyalias'] = 'sova'
|
|
config['keystorepass'] = 'r9aquRHYoI8+dYz6jKrLntQ5/NJNASFBacJh7Jv2BlI='
|
|
config['keypass'] = 'r9aquRHYoI8+dYz6jKrLntQ5/NJNASFBacJh7Jv2BlI='
|
|
signindex.config = config
|
|
|
|
logging.basicConfig(level=logging.DEBUG)
|
|
self.tempdir = tempfile.TemporaryDirectory()
|
|
os.chdir(self.tempdir.name)
|
|
self.repodir = Path('repo')
|
|
self.repodir.mkdir()
|
|
|
|
def tearDown(self):
|
|
self.tempdir.cleanup()
|
|
|
|
def test_sign_index(self):
|
|
shutil.copy(str(self.basedir / 'repo/index-v1.json'), 'repo')
|
|
signindex.sign_index(str(self.repodir), 'index-v1.json')
|
|
self.assertTrue((self.repodir / 'index-v1.jar').exists())
|
|
self.assertTrue((self.repodir / 'index-v1.json').exists())
|
|
|
|
def test_sign_index_corrupt(self):
|
|
with open('repo/index-v1.json', 'w') as fp:
|
|
fp.write('corrupt JSON!')
|
|
with self.assertRaises(json.decoder.JSONDecodeError, msg='error on bad JSON'):
|
|
signindex.sign_index(str(self.repodir), 'index-v1.json')
|
|
|
|
def test_sign_entry(self):
|
|
entry = 'repo/entry.json'
|
|
v2 = 'repo/index-v2.json'
|
|
shutil.copy(self.basedir / entry, entry)
|
|
shutil.copy(self.basedir / v2, v2)
|
|
signindex.sign_index(self.repodir, 'entry.json')
|
|
self.assertTrue((self.repodir / 'entry.jar').exists())
|
|
|
|
def test_sign_entry_corrupt(self):
|
|
"""sign_index should exit with error if entry.json is bad JSON"""
|
|
entry = 'repo/entry.json'
|
|
with open(entry, 'w') as fp:
|
|
fp.write('{')
|
|
with self.assertRaises(json.decoder.JSONDecodeError, msg='error on bad JSON'):
|
|
signindex.sign_index(self.repodir, 'entry.json')
|
|
self.assertFalse((self.repodir / 'entry.jar').exists())
|
|
|
|
def test_sign_entry_corrupt_leave_entry_jar(self):
|
|
"""sign_index should not touch existing entry.jar if entry.json is corrupt"""
|
|
existing = 'repo/entry.jar'
|
|
testvalue = "Don't touch!"
|
|
with open(existing, 'w') as fp:
|
|
fp.write(testvalue)
|
|
with open('repo/entry.json', 'w') as fp:
|
|
fp.write('{')
|
|
with self.assertRaises(json.decoder.JSONDecodeError, msg='error on bad JSON'):
|
|
signindex.sign_index(self.repodir, 'entry.json')
|
|
with open(existing) as fp:
|
|
self.assertEqual(testvalue, fp.read())
|
|
|
|
def test_sign_corrupt_index_v2_json(self):
|
|
"""sign_index should exit with error if index-v2.json JSON is corrupt"""
|
|
with open('repo/index-v2.json', 'w') as fp:
|
|
fp.write('{"key": "not really an index"')
|
|
good_entry = {
|
|
"timestamp": 1676583021000,
|
|
"version": 20002,
|
|
"index": {
|
|
"name": "/index-v2.json",
|
|
"sha256": common.sha256sum('repo/index-v2.json'),
|
|
"size": os.path.getsize('repo/index-v2.json'),
|
|
"numPackages": 0,
|
|
},
|
|
}
|
|
with open('repo/entry.json', 'w') as fp:
|
|
json.dump(good_entry, fp)
|
|
with self.assertRaises(json.decoder.JSONDecodeError, msg='error on bad JSON'):
|
|
signindex.sign_index(self.repodir, 'entry.json')
|
|
self.assertFalse((self.repodir / 'entry.jar').exists())
|
|
|
|
def test_sign_index_v2_corrupt_sha256(self):
|
|
"""sign_index should exit with error if SHA-256 of file in entry is wrong"""
|
|
entry = 'repo/entry.json'
|
|
v2 = 'repo/index-v2.json'
|
|
shutil.copy(self.basedir / entry, entry)
|
|
shutil.copy(self.basedir / v2, v2)
|
|
with open(v2, 'a') as fp:
|
|
fp.write(' ')
|
|
with self.assertRaises(exception.FDroidException, msg='error on bad SHA-256'):
|
|
signindex.sign_index(self.repodir, 'entry.json')
|
|
self.assertFalse((self.repodir / 'entry.jar').exists())
|
|
|
|
def test_signindex(self):
|
|
if common.find_apksigner({}) is None: # TODO remove me for buildserver-bullseye
|
|
self.skipTest('SKIPPING test_signindex, apksigner not installed!')
|
|
os.mkdir('archive')
|
|
metadata = Path('metadata')
|
|
metadata.mkdir()
|
|
with (metadata / 'info.guardianproject.urzip.yml').open('w') as fp:
|
|
fp.write('# placeholder')
|
|
shutil.copy(str(self.basedir / 'urzip.apk'), 'repo')
|
|
index_files = []
|
|
for f in (
|
|
'entry.jar',
|
|
'entry.json',
|
|
'index-v1.jar',
|
|
'index-v1.json',
|
|
'index-v2.json',
|
|
'index.jar',
|
|
'index.xml',
|
|
):
|
|
for section in (Path('repo'), Path('archive')):
|
|
path = section / f
|
|
self.assertFalse(path.exists(), '%s should not exist yet!' % path)
|
|
index_files.append(path)
|
|
common.options = Options
|
|
with patch('sys.argv', ['fdroid update']):
|
|
update.main()
|
|
with patch('sys.argv', ['fdroid signindex', '--verbose']):
|
|
signindex.main()
|
|
for f in index_files:
|
|
self.assertTrue(f.exists(), '%s should exist!' % f)
|
|
self.assertFalse(os.path.exists('index-v2.jar')) # no JAR version of this file
|
|
|
|
# index.jar aka v0 must by signed by SHA1withRSA
|
|
f = 'repo/index.jar'
|
|
common.verify_deprecated_jar_signature(f)
|
|
self.assertIsNone(apksigcopier.extract_v2_sig(f, expected=False))
|
|
cp = subprocess.run(
|
|
['jarsigner', '-verify', '-verbose', f], stdout=subprocess.PIPE
|
|
)
|
|
self.assertTrue(b'SHA1withRSA' in cp.stdout)
|
|
|
|
# index-v1.jar must by signed by SHA1withRSA
|
|
f = 'repo/index-v1.jar'
|
|
common.verify_deprecated_jar_signature(f)
|
|
self.assertIsNone(apksigcopier.extract_v2_sig(f, expected=False))
|
|
cp = subprocess.run(
|
|
['jarsigner', '-verify', '-verbose', f], stdout=subprocess.PIPE
|
|
)
|
|
self.assertTrue(b'SHA1withRSA' in cp.stdout)
|
|
|
|
# entry.jar aka index v2 must by signed by a modern algorithm
|
|
f = 'repo/entry.jar'
|
|
common.verify_deprecated_jar_signature(f)
|
|
self.assertIsNone(apksigcopier.extract_v2_sig(f, expected=False))
|
|
cp = subprocess.run(
|
|
['jarsigner', '-verify', '-verbose', f], stdout=subprocess.PIPE
|
|
)
|
|
self.assertFalse(b'SHA1withRSA' in cp.stdout)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
os.chdir(os.path.dirname(__file__))
|
|
|
|
import argparse
|
|
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument(
|
|
"-v",
|
|
"--verbose",
|
|
action="store_true",
|
|
default=False,
|
|
help="Spew out even more information than normal",
|
|
)
|
|
common.options = common.parse_args(parser)
|
|
|
|
newSuite = unittest.TestSuite()
|
|
newSuite.addTest(unittest.makeSuite(SignindexTest))
|
|
unittest.main(failfast=False)
|