diff options
author | Michał Górny <mgorny@gentoo.org> | 2022-09-13 17:18:30 +0200 |
---|---|---|
committer | Michał Górny <mgorny@gentoo.org> | 2022-09-13 17:18:30 +0200 |
commit | e53475a841fbe493781444b213dfd9668f4ea206 (patch) | |
tree | 24088c3d927221422e9479e0db851e520cf66c8b | |
parent | 735fb85228aad0fa5326f4235e349e360631823c (diff) | |
download | gemato-e53475a841fbe493781444b213dfd9668f4ea206.tar.gz |
Handle unknown hashes gracefully when verifying
Closes: https://github.com/projg2/gemato/issues/19
Signed-off-by: Michał Górny <mgorny@gentoo.org>
-rw-r--r-- | gemato/exceptions.py | 15 | ||||
-rw-r--r-- | gemato/manifest.py | 9 | ||||
-rw-r--r-- | gemato/verify.py | 11 | ||||
-rw-r--r-- | tests/test_recursiveloader.py | 54 |
4 files changed, 82 insertions, 7 deletions
diff --git a/gemato/exceptions.py b/gemato/exceptions.py index 6d7e8f3..db754ac 100644 --- a/gemato/exceptions.py +++ b/gemato/exceptions.py @@ -264,3 +264,18 @@ class ManifestInvalidFilename(GematoException): f'to Manifest: disallowed character ' f'U+{ord(self.filename[self.pos]):04X} at position ' f'{self.pos}') + + +class ManifestNoSupportedHashes(GematoException): + """ + An exception raised when all hashes given for an entry are + unsupported. + """ + + def __init__(self, entry): + super().__init__() + self.entry = entry + + def __str__(self): + return (f"No hashes provided for file {self.entry.path!r} are " + f"supported (out of {' '.join(self.entry.checksums)})") diff --git a/gemato/manifest.py b/gemato/manifest.py index 56f725f..2ce4811 100644 --- a/gemato/manifest.py +++ b/gemato/manifest.py @@ -1,6 +1,6 @@ # gemato: Manifest file objects # vim:fileencoding=utf-8 -# (c) 2017-2020 Michał Górny +# (c) 2017-2022 Michał Górny # Licensed under the terms of 2-clause BSD license import datetime @@ -557,3 +557,10 @@ def manifest_hashes_to_hashlib(hashes): """ for h in hashes: yield MANIFEST_HASH_MAPPING[h] + + +def is_hash_supported(h): + """ + Return True if the passed hash (in Manifest naming) is supported. + """ + return h in MANIFEST_HASH_MAPPING diff --git a/gemato/verify.py b/gemato/verify.py index 64d367e..363c9cc 100644 --- a/gemato/verify.py +++ b/gemato/verify.py @@ -1,6 +1,6 @@ # gemato: File verification routines # vim:fileencoding=utf-8 -# (c) 2017-2020 Michał Górny +# (c) 2017-2022 Michał Górny # Licensed under the terms of 2-clause BSD license import contextlib @@ -12,9 +12,10 @@ import stat from gemato.exceptions import ( ManifestCrossDevice, ManifestInvalidPath, + ManifestNoSupportedHashes, ) from gemato.hash import hash_file -from gemato.manifest import manifest_hashes_to_hashlib +from gemato.manifest import manifest_hashes_to_hashlib, is_hash_supported def get_file_metadata(path, hashes): @@ -163,7 +164,9 @@ def verify_path(path, e, expected_dev=None, last_mtime=None): checksums = () else: expect_exist = True - checksums = e.checksums + checksums = list(filter(is_hash_supported, e.checksums)) + if not checksums: + raise ManifestNoSupportedHashes(e) with contextlib.closing(get_file_metadata(path, checksums)) as g: # 1. verify whether the file existed in the first place @@ -204,7 +207,7 @@ def verify_path(path, e, expected_dev=None, last_mtime=None): diff.append(('__size__', e.size, size)) # 7. verify the checksums - for h in sorted(e.checksums): + for h in sorted(checksums): exp = e.checksums[h] got = checksums[h] if got != exp: diff --git a/tests/test_recursiveloader.py b/tests/test_recursiveloader.py index 2d2c9dc..a8ef939 100644 --- a/tests/test_recursiveloader.py +++ b/tests/test_recursiveloader.py @@ -1,6 +1,6 @@ # gemato: Recursive loader tests # vim:fileencoding=utf-8 -# (c) 2017-2020 Michał Górny +# (c) 2017-2022 Michał Górny # Licensed under the terms of 2-clause BSD license import base64 @@ -20,8 +20,9 @@ from gemato.exceptions import ( ManifestIncompatibleEntry, ManifestCrossDevice, ManifestSymlinkLoop, + ManifestNoSupportedHashes, ) -from gemato.manifest import ManifestPathEntry +from gemato.manifest import ManifestPathEntry, ManifestFileEntry from gemato.recursiveloader import ManifestRecursiveLoader from tests.test_compression import COMPRESSION_ALGOS @@ -788,6 +789,32 @@ DATA test 11 SHA1 561295c9cbf9d6b2f6428414504a8deed3020641 f'TIMESTAMP {future_dt.strftime("%Y-%m-%dT%H:%M:%SZ")}')) +class UnknownHashLayout(BaseLayout): + """Layout with a supported and unknown hash""" + + MANIFESTS = { + 'Manifest': ''' +DATA test 0 MD5 d41d8cd98f00b204e9800998ecf8427e X-UNKNOWN 0123456789abcdef +''', + } + FILES = { + 'test': '', + } + + +class UnknownHashOnlyLayout(BaseLayout): + """Layout with unknown hash only""" + + MANIFESTS = { + 'Manifest': ''' +DATA test 0 X-UNKNOWN 0123456789abcdef +''', + } + FILES = { + 'test': '', + } + + FLAT_LAYOUTS = [ DuplicateEntryLayout, DuplicateEbuildEntryLayout, @@ -818,6 +845,8 @@ FLAT_LAYOUTS = [ SymlinkLoopLayout, SymlinkLoopIgnoreLayout, MismatchedFileLayout, + UnknownHashLayout, + UnknownHashOnlyLayout, ] SUB_LAYOUTS = [ SubTimestampLayout, @@ -1069,6 +1098,7 @@ COMMON_VERIFY_PATH_VARIANTS = [ [('MD5', '5f8db599de986fab7a21625b7916589c', '6f8db599de986fab7a21625b7916589c')]), + (UnknownHashLayout, 'test', True, []), ] @@ -1151,6 +1181,8 @@ def test_assert_path_verifies(layout_factory, layout, path, expected, diff): {'': {'metadata.xml': ('DATA', 'metadata.xml', ['MD5'])}}), (IncompatibleTypeLayout, '', 'MISC', {'': {'metadata.xml': ('MISC', 'metadata.xml', ['MD5'])}}), + (UnknownHashLayout, '', None, + {'': {'test': ('DATA', 'test', ['MD5', 'X-UNKNOWN'])}}), ]) def test_get_file_entry_dict(layout_factory, layout, path, only_types, expected): @@ -1281,6 +1313,7 @@ COMMON_DIRECTORY_VERIFICATION_VARIANTS = [ [('MD5', '5f8db599de986fab7a21625b7916589c', '6f8db599de986fab7a21625b7916589c')]), + (UnknownHashLayout, '', None, []), ] @@ -1323,6 +1356,7 @@ COMMON_DIRECTORY_VERIFICATION_VARIANTS = [ (SymlinkLoopLayout, '', lambda e: False, ManifestSymlinkLoop, None, []), (SymlinkLoopIgnoreLayout, '', None, True, None, []), + (UnknownHashOnlyLayout, '', None, ManifestNoSupportedHashes, None, []), ]) def test_assert_directory_verifies(layout_factory, layout, path, fail_handler, expected, fail_path, diff): @@ -1380,6 +1414,9 @@ def test_assert_directory_verifies(layout_factory, layout, path, fail_handler, (SymlinkLoopLayout, '', '', str(ManifestSymlinkLoop('<path>')).split('<path>', 1)[1]), (SymlinkLoopIgnoreLayout, '', '', None), + (UnknownHashOnlyLayout, '', '', + str(ManifestNoSupportedHashes(ManifestFileEntry( + 'test', 0, {"X-UNKNOWN": ""})))), ]) def test_cli_verify(layout_factory, caplog, layout, path, args, expected): tmp_path = layout_factory.create(layout, readonly=True) @@ -1942,6 +1979,19 @@ def test_update_entry_raise(layout_factory, layout, path, expected, reason): @pytest.mark.parametrize( + 'layout,path', + [(UnknownHashLayout, 'test'), + (UnknownHashOnlyLayout, 'test'), + ]) +def test_update_entry_unknown_hash(layout_factory, layout, path): + tmp_path = layout_factory.create(layout) + m = ManifestRecursiveLoader(tmp_path / layout.TOP_MANIFEST, + allow_xdev=False) + with pytest.raises(KeyError) as exc: + m.update_entry_for_path(path) + + +@pytest.mark.parametrize( 'layout,path,expected', [(BasicTestLayout, 'nonexist', FileNotFoundError), (CrossDeviceLayout, '', ManifestCrossDevice), |