Skip to content

Commit eaeec27

Browse files
authored
Merge pull request #222 from tommycbird/fix/deploy-checksum-flag
Fix checksum generation for FIPS-compliant systems by supporting 'usedforsecurity=False' in hash functions
2 parents be1de3b + 20c6d38 commit eaeec27

File tree

2 files changed

+65
-3
lines changed

2 files changed

+65
-3
lines changed

pyartifactory/models/artifact.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import hashlib
77
from datetime import datetime
88
from pathlib import Path
9-
from typing import Any, Callable, Dict, List, Literal, Optional, Union
9+
from typing import Callable, Dict, List, Literal, Optional, Union
1010

1111
from pydantic import BaseModel
1212

@@ -18,14 +18,28 @@ class Checksums(BaseModel):
1818
md5: str
1919
sha256: str
2020

21+
@staticmethod
22+
def get_hasher(func: Callable[..., hashlib._Hash]) -> hashlib._Hash:
23+
# In Python 3.9+, some hash algorithms (like md5, sha1, sha256) may be disabled in FIPS-compliant systems
24+
# unless 'usedforsecurity=False' is specified. This flag allows the hash function to be used for non-security
25+
# purposes such as checksums, even in FIPS environments.
26+
try:
27+
return func(usedforsecurity=False)
28+
except TypeError:
29+
return func()
30+
2131
@classmethod
2232
def generate(cls, file_: Path) -> Checksums:
2333
block_size: int = 65536
24-
mapping: dict[str, Callable[[], Any]] = {"md5": hashlib.md5, "sha1": hashlib.sha1, "sha256": hashlib.sha256}
34+
mapping = {
35+
"md5": hashlib.md5,
36+
"sha1": hashlib.sha1,
37+
"sha256": hashlib.sha256,
38+
}
2539
results = {}
2640

2741
for algorithm, hashing_function in mapping.items():
28-
hasher = hashing_function()
42+
hasher = cls.get_hasher(hashing_function)
2943
with file_.absolute().open("rb") as fd:
3044
buf = fd.read(block_size)
3145
while len(buf) > 0:

tests/test_artifacts.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -612,6 +612,54 @@ def test_checksum_defined_file(file_path: Path, expected_sha1: str, expected_md5
612612
assert result == expected
613613

614614

615+
def test_get_hasher_security_flag():
616+
calls = {"kwargs": None}
617+
618+
class Dummy:
619+
def __init__(self):
620+
self._buf = b""
621+
622+
def update(self, b):
623+
self._buf += b
624+
625+
def hexdigest(self):
626+
return "ok"
627+
628+
def func(**kwargs):
629+
calls["kwargs"] = kwargs
630+
return Dummy()
631+
632+
hasher = Checksums.get_hasher(func)
633+
assert isinstance(hasher, Dummy)
634+
assert calls["kwargs"] == {"usedforsecurity": False}
635+
636+
637+
def test_get_hasher_type_error_thrown():
638+
calls = {"with_kwargs": 0, "without_kwargs": 0}
639+
640+
class Dummy:
641+
def __init__(self):
642+
self._buf = b""
643+
644+
def update(self, b):
645+
self._buf += b
646+
647+
def hexdigest(self):
648+
return "ok"
649+
650+
def func(**kwargs):
651+
if kwargs:
652+
calls["with_kwargs"] += 1
653+
raise TypeError("unexpected kwarg")
654+
calls["without_kwargs"] += 1
655+
return Dummy()
656+
657+
hasher = Checksums.get_hasher(func)
658+
assert isinstance(hasher, Dummy)
659+
assert calls["with_kwargs"] == 1
660+
assert calls["without_kwargs"] == 1
661+
662+
615663
@responses.activate
616664
def test_deploy_artifact_with_checksum_success(mocker):
617665
responses.add(responses.PUT, f"{URL}/{ARTIFACT_PATH}", status=200)

0 commit comments

Comments
 (0)