diff options
author | Michał Górny <mgorny@gentoo.org> | 2022-09-16 08:37:12 +0200 |
---|---|---|
committer | Michał Górny <mgorny@gentoo.org> | 2022-09-16 08:37:12 +0200 |
commit | 64fa0cc30b2be8323343fac425abc733113b3450 (patch) | |
tree | bd1581b43cd4c4864f4ce5a425d1d8c88552917e | |
parent | e238b8bffea1296a3d0c292c616158dacd211bdd (diff) | |
download | gemato-64fa0cc30b2be8323343fac425abc733113b3450.tar.gz |
Support rejecting insecure hashes when updating
Signed-off-by: Michał Górny <mgorny@gentoo.org>
-rw-r--r-- | gemato/exceptions.py | 16 | ||||
-rw-r--r-- | gemato/manifest.py | 8 | ||||
-rw-r--r-- | gemato/verify.py | 15 | ||||
-rw-r--r-- | tests/test_verify.py | 24 |
4 files changed, 59 insertions, 4 deletions
diff --git a/gemato/exceptions.py b/gemato/exceptions.py index db754ac..ad9002a 100644 --- a/gemato/exceptions.py +++ b/gemato/exceptions.py @@ -1,6 +1,6 @@ # gemato: exceptions # 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 class GematoException(Exception): @@ -279,3 +279,17 @@ class ManifestNoSupportedHashes(GematoException): def __str__(self): return (f"No hashes provided for file {self.entry.path!r} are " f"supported (out of {' '.join(self.entry.checksums)})") + + +class ManifestInsecureHashes(GematoException): + """ + An exception raised when some of the requested hashes are insecure. + """ + + def __init__(self, hashes): + super().__init__() + self.hashes = hashes + + def __str__(self): + return ("Some of the requested hashes are considered insecure: " + f"{' '.join(self.hashes)}") diff --git a/gemato/manifest.py b/gemato/manifest.py index 2ce4811..d62fa14 100644 --- a/gemato/manifest.py +++ b/gemato/manifest.py @@ -564,3 +564,11 @@ def is_hash_supported(h): Return True if the passed hash (in Manifest naming) is supported. """ return h in MANIFEST_HASH_MAPPING + + +def is_hash_secure(h): + """ + Return True if the passed hash (in Manifest naming) is considered + cryptographically secure. + """ + return h not in ("MD5", "SHA1") diff --git a/gemato/verify.py b/gemato/verify.py index b54c946..44a639f 100644 --- a/gemato/verify.py +++ b/gemato/verify.py @@ -13,9 +13,14 @@ from gemato.exceptions import ( ManifestCrossDevice, ManifestInvalidPath, ManifestNoSupportedHashes, + ManifestInsecureHashes, ) from gemato.hash import hash_file -from gemato.manifest import manifest_hashes_to_hashlib, is_hash_supported +from gemato.manifest import ( + manifest_hashes_to_hashlib, + is_hash_supported, + is_hash_secure, + ) def get_file_metadata(path, hashes): @@ -220,7 +225,7 @@ def verify_path(path, e, expected_dev=None, last_mtime=None): def update_entry_for_path(path, e, hashes=None, expected_dev=None, - last_mtime=None): + last_mtime=None, require_secure_hashes=False): """ Update the data in entry @e to match the current state of file at path @path. Uses hashes listed in @hashes (using Manifest names), @@ -241,12 +246,18 @@ def update_entry_for_path(path, e, hashes=None, expected_dev=None, If @last_mtime is not None, it specifies the timestamp corresponding to the previous file update. If the file is not newer than that, the checksum calculation is skipped. + + If @require_secure_hashes is True, only secure hashes can be used. """ assert e.tag not in ('IGNORE', 'TIMESTAMP') if hashes is None: hashes = list(e.checksums) + if require_secure_hashes: + insecure = list(filter(lambda x: not is_hash_secure(x), hashes)) + if insecure: + raise ManifestInsecureHashes(insecure) with contextlib.closing(get_file_metadata(path, hashes)) as g: # 1. verify whether the file existed in the first place diff --git a/tests/test_verify.py b/tests/test_verify.py index 1f3170b..7ee4545 100644 --- a/tests/test_verify.py +++ b/tests/test_verify.py @@ -1,8 +1,9 @@ # gemato: Verification 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 contextlib import itertools import os import os.path @@ -14,6 +15,7 @@ import pytest from gemato.exceptions import ( ManifestInvalidPath, ManifestCrossDevice, + ManifestInsecureHashes, ) from gemato.hash import hash_path from gemato.manifest import new_manifest_entry @@ -444,6 +446,26 @@ def test_unreadable_file(test_tree, function, args): @pytest.mark.parametrize( + "entry_hash,hashes_arg,insecure", + [("MD5", None, True), + ("SHA1", None, True), + ("SHA512", None, False), + ("MD5", "SHA1 SHA512", True), + ("MD5", "SHA512", False), + ("SHA512", "MD5 SHA512", True), + ]) +def test_insecure_hashes(test_tree, entry_hash, hashes_arg, insecure): + ctx = (pytest.raises(ManifestInsecureHashes) if insecure + else contextlib.nullcontext()) + with ctx: + update_entry_for_path( + test_tree / "empty-file", + new_manifest_entry("DATA", "empty-file", 0, {entry_hash: ""}), + hashes=hashes_arg.split() if hashes_arg else None, + require_secure_hashes=True) + + +@pytest.mark.parametrize( 'a_cls,a_name,a_args,b_cls,b_name,b_args,expected,diff', [('DATA', 'test', [0, {'MD5': 'd41d8cd98f00b204e9800998ecf8427e'}], 'DATA', 'test', [0, {'MD5': 'd41d8cd98f00b204e9800998ecf8427e'}], |