summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gemato/exceptions.py16
-rw-r--r--gemato/manifest.py8
-rw-r--r--gemato/verify.py15
-rw-r--r--tests/test_verify.py24
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'}],