Files
autospec/tests/test_pkg_integrity.py
William Douglas 90bcff8bf9 Remove pypi integrity checks
pypi is deprecating the API and doesn't recommend its usage for
integrity checks so stop trying to make use of it in autospec.

Signed-off-by: William Douglas <william.douglas@intel.com>
2023-05-30 15:41:38 -07:00

451 lines
20 KiB
Python

from io import BytesIO
import json
import os
import shutil
import tempfile
import unittest
from unittest.mock import patch
import config
import download
import pkg_integrity
TESTDIR = os.path.join(os.getcwd(), "tests/testfiles/pkg_integrity")
TESTKEYDIR = os.path.join(TESTDIR, "testkeys")
PACKAGE_URL = "http://pkgconfig.freedesktop.org/releases/pkg-config-0.29.1.tar.gz"
NO_SIGN_PKT_URL = "http://www.ferzkopp.net/Software/SDL_gfx-2.0/SDL_gfx-2.0.25.tar.gz"
NOSIGN_PKT_URL_BAD = "http://gnu.mirrors.pair.com/savannah/savannah/quagga/bad_quagga-1.1.0.tar.gz"
NOSIGN_PKT_URL = "http://download.savannah.gnu.org/releases/quagga/quagga-1.1.0.tar.gz"
NOSIGN_SIGN_URL = "http://download.savannah.gnu.org/releases/quagga/quagga-1.1.0.tar.gz.asc"
GNOME_SHA256_PKG = "https://download.gnome.org/sources/pygobject/3.24/pygobject-3.24.0.tar.xz"
QT_SHA256_PKG = "https://download.qt.io/official_releases/qt/5.12/5.12.4/submodules/qtspeech-everywhere-src-5.12.4.tar.xz"
KEYID = "EC2392F2EDE74488680DA3CF5F2B4756ED873D23"
def mock_download_do_curl(url, dst=None):
bad_sigs = ["http://pkgconfig.freedesktop.org/releases/pkg-config-0.29.1.tar.gz.sig",
"http://www.ferzkopp.net/Software/SDL_gfx-2.0/SDL_gfx-2.0.25.tar.gz.sig",
"http://www.ferzkopp.net/Software/SDL_gfx-2.0/SDL_gfx-2.0.25.tar.gz.asc",
"http://www.ferzkopp.net/Software/SDL_gfx-2.0/SDL_gfx-2.0.25.tar.gz.sign"]
if not dst:
return BytesIO(b'foobar')
src = os.path.join(TESTDIR, os.path.basename(url))
if dst and os.path.isfile(src):
shutil.copyfile(src, dst)
return dst
else:
return None
if url in bad_sigs:
return None
@patch('download.do_curl', mock_download_do_curl)
class TestCheckFn(unittest.TestCase):
def test_check_matching_sign_url(self):
with tempfile.TemporaryDirectory() as tmpd:
conf = config.Config(tmpd)
conf.rewrite_config_opts = unittest.mock.Mock()
conf.config_opts['verify_required'] = False
pkey = "023A4420C7EC6914.pkey"
shutil.copy(os.path.join(TESTKEYDIR, pkey), tmpd)
shutil.copy(os.path.join(TESTDIR, os.path.basename(PACKAGE_URL)), tmpd)
shutil.copy(os.path.join(TESTDIR, os.path.basename(PACKAGE_URL)) + ".asc", tmpd)
result = pkg_integrity.check(PACKAGE_URL, conf)
self.assertTrue(result)
def test_check_with_existing_sign(self):
""" Download signature for local verification """
with tempfile.TemporaryDirectory() as tmpd:
conf = config.Config(tmpd)
conf.rewrite_config_opts = unittest.mock.Mock()
conf.config_opts['verify_required'] = False
shutil.copy(os.path.join(TESTKEYDIR, "6FE57CA8C1A4AEA6.pkey"), tmpd)
shutil.copy(os.path.join(TESTDIR, os.path.basename(NOSIGN_PKT_URL)), tmpd)
result = pkg_integrity.check(NOSIGN_PKT_URL, conf)
self.assertTrue(result)
@patch('download.do_curl', mock_download_do_curl)
class TestDomainBasedVerifiers(unittest.TestCase):
def run_test_for_domain(self, Verifier, url):
with tempfile.TemporaryDirectory() as tmpd:
filen = os.path.basename(url)
shutil.copy(os.path.join(TESTDIR, filen), tmpd)
package_path = os.path.join(tmpd, filen)
verifier = Verifier(**{'package_path': package_path,
'url': url})
return verifier.verify()
return None
def _mock_fetch_shasum(url):
return (
"100395496483fcea7ba03fc1655c7a770f7f2e12e93be8bda2e31fec42debde0 pygobject-3.24.0.news\n"
"ae417db3be2a197b403bba6472cfb35a6e642cd802660832acb9c96123f79463 pygobject-3.24.0.changes\n"
"4e228b1c0f36e810acd971fad1c7030014900d8427c308d63a560f3f1037fa3c pygobject-3.24.0.tar.xz"
)
@patch('pkg_integrity.GnomeOrgVerifier.fetch_shasum', _mock_fetch_shasum)
def test_gnome_org(self):
result = self.run_test_for_domain(pkg_integrity.GnomeOrgVerifier, GNOME_SHA256_PKG)
self.assertTrue(result)
@patch('pkg_integrity.QtIoVerifier.fetch_shasum')
def test_qt_io(self, test_fetch):
test_fetch.return_value = "2ff9660fb3f5663c9161f491d1a304db62691720136ae22c145ef6a1c94b90ec qtspeech-everywhere-src-5.12.4.tar.xz\n"
result = self.run_test_for_domain(pkg_integrity.QtIoVerifier, QT_SHA256_PKG)
self.assertTrue(result)
@patch('download.do_curl', mock_download_do_curl)
class TestGPGVerifier(unittest.TestCase):
def test_from_url(self):
with tempfile.TemporaryDirectory() as tmpd:
conf = config.Config(tmpd)
conf.rewrite_config_opts = unittest.mock.Mock()
conf.config_opts['verify_required'] = False
shutil.copy(os.path.join(TESTKEYDIR, "023A4420C7EC6914.pkey"), tmpd)
shutil.copy(os.path.join(TESTDIR, os.path.basename(PACKAGE_URL)), tmpd)
result = pkg_integrity.check(PACKAGE_URL, conf)
self.assertTrue(result)
def test_invalid_key(self):
with tempfile.TemporaryDirectory() as tmpd:
conf = config.Config(tmpd)
conf.rewrite_config_opts = unittest.mock.Mock()
conf.config_opts['verify_required'] = False
shutil.copy(os.path.join(TESTKEYDIR, "6FE57CA8C1A4AEA6.pkey"), tmpd)
shutil.copy(os.path.join(TESTDIR, os.path.basename(NOSIGN_PKT_URL_BAD)), tmpd)
with open(os.path.join(tmpd, os.path.basename(NOSIGN_PKT_URL_BAD) + ".asc"), 'w') as ofile:
ofile.write("Invalid signature")
result = pkg_integrity.check(NOSIGN_PKT_URL_BAD, conf)
self.assertIsNone(result)
def test_key_not_found(self):
with tempfile.TemporaryDirectory() as tmpd:
conf = config.Config(tmpd)
conf.rewrite_config_opts = unittest.mock.Mock()
conf.config_opts['verify_required'] = False
shutil.copy(os.path.join(TESTKEYDIR, "6FE57CA8C1A4AEA6.pkey"), tmpd)
shutil.copy(os.path.join(TESTDIR, os.path.basename(NOSIGN_PKT_URL_BAD)), tmpd)
result = pkg_integrity.check(NOSIGN_PKT_URL_BAD, conf)
self.assertIsNone(result)
def test_from_disk(self):
with tempfile.TemporaryDirectory() as tmpd:
conf = config.Config(tmpd)
conf.rewrite_config_opts = unittest.mock.Mock()
conf.config_opts['verify_required'] = False
shutil.copy(os.path.join(TESTKEYDIR, "023A4420C7EC6914.pkey"), tmpd)
out_file = os.path.join(tmpd, os.path.basename(PACKAGE_URL))
out_key = out_file + ".asc"
shutil.copy(os.path.join(TESTDIR, os.path.basename(PACKAGE_URL)), tmpd)
shutil.copy(os.path.join(TESTDIR, os.path.basename(PACKAGE_URL)) + ".asc", tmpd)
result = pkg_integrity.from_disk(PACKAGE_URL, out_file, out_key, conf)
self.assertTrue(result)
def test_non_matchingsig(self):
with tempfile.TemporaryDirectory() as tmpd:
conf = config.Config(tmpd)
conf.rewrite_config_opts = unittest.mock.Mock()
conf.config_opts['verify_required'] = False
shutil.copy(os.path.join(TESTKEYDIR, "023A4420C7EC6914.pkey"), tmpd)
out_file = os.path.join(tmpd, os.path.basename(PACKAGE_URL))
f = open(out_file, 'wb')
f.write(b'made up date that will fail check')
f.close()
with self.assertRaises(SystemExit) as a:
pkg_integrity.check(PACKAGE_URL, conf)
self.assertEqual(a.exception.code, 1)
def test_result_on_non_existent_pkg_path(self):
conf = config.Config('')
conf.rewrite_config_opts = unittest.mock.Mock()
conf.config_opts['verify_required'] = False
result = pkg_integrity.from_disk('http://nokey.com/package.tar.gz',
'NonExistentPKG.tar.gz',
'NonExistentKey.asc',
conf)
self.assertIsNone(result)
def test_result_on_nosign_package(self):
with tempfile.TemporaryDirectory() as tmpd:
conf = config.Config(tmpd)
conf.rewrite_config_opts = unittest.mock.Mock()
conf.config_opts['verify_required'] = False
shutil.copy(os.path.join(TESTDIR, os.path.basename(NOSIGN_PKT_URL)), tmpd)
result = pkg_integrity.check(NO_SIGN_PKT_URL, conf)
self.assertIsNone(result)
@patch.object(pkg_integrity.GPGCli, 'exec_cmd')
@patch('pkg_integrity.parse_gpg_packets')
def test_result_multiple_sig(self, mock_parse, mock_exec):
"""Test verification of first signature of tarball with multiple signatures."""
def packets_separator(filename, **kwargs):
if filename.endswith('.pkey'):
packets = [
{
'offset': 528,
'length': 37,
'type': 'user ID',
'email': 'user1@example.com',
},
]
elif filename.endswith('.asc'):
packets = [
{
'offset': 0,
'length': 543,
'type': 'signature',
'keyid': '023A4420C7EC6914',
},
{
'offset': 543,
'length': 543,
'type': 'signature',
'keyid': '12345678DEADCAFE',
},
]
return packets
mock_parse.side_effect = packets_separator
mock_exec.return_value = (b'', b'', 0)
with tempfile.TemporaryDirectory() as tmpd:
conf = config.Config(tmpd)
conf.rewrite_config_opts = unittest.mock.Mock()
conf.config_opts['verify_required'] = False
shutil.copy(os.path.join(TESTKEYDIR, "023A4420C7EC6914.pkey"), tmpd)
shutil.copy(os.path.join(TESTDIR, os.path.basename(PACKAGE_URL)), tmpd)
result = pkg_integrity.check(PACKAGE_URL, conf)
self.assertTrue(result)
self.assertEqual(mock_parse.call_count, 4)
self.assertEqual(mock_exec.call_count, 3)
self.assertEqual(pkg_integrity.EMAIL, "user1@example.com")
self.assertEqual(pkg_integrity.KEYID, "023A4420C7EC6914")
@patch.object(pkg_integrity.GPGCli, 'exec_cmd')
@patch('pkg_integrity.parse_gpg_packets')
def test_result_multiple_sig_no_separators(self, mock_parse, mock_exec):
"""Test skipping of sig verification in the multiple sig case when packet separators are absent."""
def packets_no_separator(filename, **kwargs):
if filename.endswith('.pkey'):
packets = [
{
'type': 'user ID',
'email': 'user2@example.com',
},
]
elif filename.endswith('.asc'):
packets = [
{
'type': 'signature',
'keyid': '023A4420C7EC6914',
},
{
'type': 'signature',
'keyid': 'DEADCAFEC7EC6914',
},
]
return packets
mock_parse.side_effect = packets_no_separator
mock_exec.return_value = (b'', b'', 0)
with tempfile.TemporaryDirectory() as tmpd:
conf = config.Config(tmpd)
conf.rewrite_config_opts = unittest.mock.Mock()
conf.config_opts['verify_required'] = False
shutil.copy(os.path.join(TESTKEYDIR, "023A4420C7EC6914.pkey"), tmpd)
shutil.copy(os.path.join(TESTDIR, os.path.basename(PACKAGE_URL)), tmpd)
with self.assertRaises(SystemExit) as msg:
result = pkg_integrity.check(PACKAGE_URL, conf)
self.assertEqual(msg.exception.code, 1)
self.assertEqual(mock_parse.call_count, 4)
self.assertEqual(mock_exec.call_count, 2)
self.assertEqual(pkg_integrity.EMAIL, "user2@example.com")
self.assertEqual(pkg_integrity.KEYID, "023A4420C7EC6914")
class TestInputGetter(unittest.TestCase):
def test_timput(self):
ig = pkg_integrity.InputGetter(default='N', timeout=2)
answer = ig.get_answer()
self.assertIsNone(answer)
ig = pkg_integrity.InputGetter(default='Y', timeout=2)
answer = ig.get_answer()
self.assertIsNone(answer)
class TestUtils(unittest.TestCase):
def test_get_verifier(self):
x = pkg_integrity.get_verifier('file.abcd')
self.assertEqual(x, None)
y = pkg_integrity.get_verifier('xorriso-1.4.6.tar.gz')(
package_path='',
url='http://ftp.gnu.org/gnu/xorriso/xorriso-1.4.6.tar.gz',
package_check='http://ftp.gnu.org/gnu/xorriso/xorriso-1.4.6.tar.gz.asc'
)
self.assertTrue(isinstance(y, pkg_integrity.GPGVerifier))
def test_parse_gpg_packets_for_keyid(self):
"""Test parse_gpg_packets() to retrieve keyid info."""
def check_packets(algo, key_id, packet_count, packet_with_val):
with tempfile.NamedTemporaryFile(delete=True) as tmpf:
tmpf.write(algo)
tmpf.flush()
packets = pkg_integrity.parse_gpg_packets(tmpf.name)
self.assertIsNotNone(packets)
self.assertEqual(len(packets), packet_count)
self.assertEqual(packets[packet_with_val]["keyid"], key_id)
tmpf.close()
check_packets(KEY_ALGO17, '8AFAFCD242818A52', 6, 1)
check_packets(KEY_ALGO1, '330239C1C4DAFEE1', 1, 0)
def test_get_keyid(self):
"""Test get_keyid() to retrieve key ID from GPG key or signature."""
def check_get_keyid(algo, key_id):
with tempfile.NamedTemporaryFile(delete=True) as tmpf:
tmpf.write(algo)
tmpf.flush()
result = pkg_integrity.get_keyid(tmpf.name)
self.assertEqual(result, key_id)
tmpf.close()
check_get_keyid(KEY_ALGO17, '8AFAFCD242818A52')
check_get_keyid(KEY_ALGO1, '330239C1C4DAFEE1')
def test_get_keyid_none(self):
"""Test get_keyid() when the key name is invalid."""
false_name = '/false/name'
self.assertTrue(pkg_integrity.get_keyid(false_name) is None)
def _mock_download_file(url, dst=None):
# make return codes match by url to ensure we are using the expected signature type
if url in ("http://ftp.gnu.org/pub/gnu/gperf/gperf-3.0.4.tar.gz.sig",
"http://download.savannah.gnu.org/releases/quilt/quilt-0.65.tar.gz.asc",
"http://download.savannah.gnu.org/releases/freetype/freetype-2.9.tar.bz2.sign"):
return os.path.join(dst, os.path.basename(url))
return None
@patch('download.do_curl', _mock_download_file)
def test_get_signature_url(self):
url_from_gnu = "http://ftp.gnu.org/pub/gnu/gperf/gperf-3.0.4.tar.gz"
url_from_gnu1 = "http://download.savannah.gnu.org/releases/quilt/quilt-0.65.tar.gz"
url_from_gnu2 = "http://download.savannah.gnu.org/releases/freetype/freetype-2.9.tar.bz2"
self.assertEqual(pkg_integrity.get_signature_file(url_from_gnu, '.')[-4:], '.sig')
self.assertEqual(pkg_integrity.get_signature_file(url_from_gnu1, '.')[-4:], '.asc')
self.assertEqual(pkg_integrity.get_signature_file(url_from_gnu2, '.')[-5:], '.sign')
def test_parse_gpg_packets_for_email(self):
"""Test parse_gpg_packets() to retrieve email info."""
def check_packets(algo, email, packet_count, packet_with_val):
with tempfile.NamedTemporaryFile(delete=True) as tmpf:
tmpf.write(algo)
tmpf.flush()
packets = pkg_integrity.parse_gpg_packets(tmpf.name)
self.assertIsNotNone(packets)
self.assertEqual(len(packets), packet_count)
if packet_with_val:
self.assertEqual(packets[packet_with_val]["email"], email)
tmpf.close()
check_packets(KEY_ALGO17, 'kislyuk@gmail.com', 6, 0)
check_packets(KEY_ALGO1, None, 1, None)
def test_get_email(self):
"""Test get_email() to retrieve email info from GPG key."""
def check_get_email(algo, email):
with tempfile.NamedTemporaryFile(delete=True) as tmpf:
tmpf.write(algo)
tmpf.flush()
result = pkg_integrity.get_email(tmpf.name)
self.assertEqual(result, email)
tmpf.close()
check_get_email(KEY_ALGO17, 'kislyuk@gmail.com')
check_get_email(KEY_ALGO1, None)
KEY_ALGO1 = b"""\
-----BEGIN PGP SIGNATURE-----
iQEcBAABAgAGBQJX1yrCAAoJEDMCOcHE2v7hwhcH/AqhJ/vqkCOo09Yh89bYRFyb
O5yEpaRV7vo4qoqXht3fQiR3KC3lSKybtScihbQ0xcTeBzSwGPMKPRpOqXEKRnwY
9Zq8ev72Ixi5yVsKdSKjoeM4smXJdQolKnrKy0chsOMzu7cxk7hwejplIMjycKza
g3HM6jtw6v10JDj6a5SJPkrufj2eIHq1enn2di9q9+yyAJGiuWi3ABfINmL3Y9sa
zODoMBD/B7LCZ1Zv6dG7kaN1XS82crOmXrtgcWslKxfGPkdW9SGQdhCtm6f/Z5/w
29adzXFicobZBFBHVnbQ0iRf39omkNxPOhMYwLVQFprzjOPm/DDHSztZj87jbOM=
=Qe1y
-----END PGP SIGNATURE-----
"""
KEY_ALGO17 = b"""\
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v1
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
qA34waak3JSxv16lZC0TqIuD
=cHO/
-----END PGP PUBLIC KEY BLOCK-----
"""
if __name__ == '__main__':
unittest.main(buffer=True)