Skip to content

Commit 2478f54

Browse files
authored
publishing: allow publishing under FIPS environments
This change introduces HashManager to disable md5 and blake2 when publishing under FIPS enabled environments. Resolves: #8310
1 parent 52e06eb commit 2478f54

File tree

3 files changed

+155
-15
lines changed

3 files changed

+155
-15
lines changed

src/poetry/publishing/hash_manager.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
from __future__ import annotations
2+
3+
import hashlib
4+
import io
5+
6+
from contextlib import suppress
7+
from typing import TYPE_CHECKING
8+
from typing import NamedTuple
9+
10+
11+
if TYPE_CHECKING:
12+
from pathlib import Path
13+
14+
15+
class Hexdigest(NamedTuple):
16+
md5: str | None
17+
sha256: str | None
18+
blake2_256: str | None
19+
20+
21+
class HashManager:
22+
def __init__(self) -> None:
23+
self._sha2_hasher = hashlib.sha256()
24+
25+
self._md5_hasher = None
26+
with suppress(ValueError):
27+
# FIPS mode disables MD5
28+
self._md5_hasher = hashlib.md5()
29+
30+
self._blake_hasher = None
31+
with suppress(ValueError, TypeError):
32+
# FIPS mode disables blake2
33+
self._blake_hasher = hashlib.blake2b(digest_size=256 // 8)
34+
35+
def _md5_update(self, content: bytes) -> None:
36+
if self._md5_hasher is not None:
37+
self._md5_hasher.update(content)
38+
39+
def _md5_hexdigest(self) -> str | None:
40+
if self._md5_hasher is not None:
41+
return self._md5_hasher.hexdigest()
42+
return None
43+
44+
def _blake_update(self, content: bytes) -> None:
45+
if self._blake_hasher is not None:
46+
self._blake_hasher.update(content)
47+
48+
def _blake_hexdigest(self) -> str | None:
49+
if self._blake_hasher is not None:
50+
return self._blake_hasher.hexdigest()
51+
return None
52+
53+
def hash(self, file: Path) -> None:
54+
with file.open("rb") as fp:
55+
for content in iter(lambda: fp.read(io.DEFAULT_BUFFER_SIZE), b""):
56+
self._md5_update(content)
57+
self._sha2_hasher.update(content)
58+
self._blake_update(content)
59+
60+
def hexdigest(self) -> Hexdigest:
61+
return Hexdigest(
62+
self._md5_hexdigest(),
63+
self._sha2_hasher.hexdigest(),
64+
self._blake_hexdigest(),
65+
)

src/poetry/publishing/uploader.py

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
from __future__ import annotations
22

3-
import hashlib
4-
import io
5-
63
from pathlib import Path
74
from typing import TYPE_CHECKING
85
from typing import Any
@@ -18,6 +15,7 @@
1815
from requests_toolbelt.multipart import MultipartEncoderMonitor
1916

2017
from poetry.__version__ import __version__
18+
from poetry.publishing.hash_manager import HashManager
2119
from poetry.utils.constants import REQUESTS_TIMEOUT
2220
from poetry.utils.patterns import wheel_file_re
2321

@@ -126,19 +124,13 @@ def post_data(self, file: Path) -> dict[str, Any]:
126124

127125
file_type = self._get_type(file)
128126

129-
blake2_256_hash = hashlib.blake2b(digest_size=256 // 8)
127+
hash_manager = HashManager()
128+
hash_manager.hash(file)
129+
file_hashes = hash_manager.hexdigest()
130130

131-
md5_hash = hashlib.md5()
132-
sha256_hash = hashlib.sha256()
133-
with file.open("rb") as fp:
134-
for content in iter(lambda: fp.read(io.DEFAULT_BUFFER_SIZE), b""):
135-
md5_hash.update(content)
136-
sha256_hash.update(content)
137-
blake2_256_hash.update(content)
138-
139-
md5_digest = md5_hash.hexdigest()
140-
sha2_digest = sha256_hash.hexdigest()
141-
blake2_256_digest = blake2_256_hash.hexdigest()
131+
md5_digest = file_hashes.md5
132+
sha2_digest = file_hashes.sha256
133+
blake2_256_digest = file_hashes.blake2_256
142134

143135
py_version: str | None = None
144136
if file_type == "bdist_wheel":

tests/publishing/test_hash_manager.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
from __future__ import annotations
2+
3+
from typing import TYPE_CHECKING
4+
from typing import Any
5+
6+
import pytest
7+
8+
from poetry.publishing.hash_manager import HashManager
9+
10+
11+
if TYPE_CHECKING:
12+
from pathlib import Path
13+
14+
from pytest_mock import MockerFixture
15+
16+
from tests.types import FixtureDirGetter
17+
18+
19+
@pytest.fixture
20+
def distributions_dir(fixture_dir: FixtureDirGetter) -> Path:
21+
return fixture_dir("distributions")
22+
23+
24+
@pytest.mark.parametrize(
25+
"file, hashes",
26+
(
27+
(
28+
"demo-0.1.0.tar.gz",
29+
(
30+
"d1912c917363a64e127318655f7d1fe7",
31+
"9fa123ad707a5c6c944743bf3e11a0e80d86cb518d3cf25320866ca3ef43e2ad",
32+
"cb638093d63df647e70b03e963bedc31e021cb088695e29101b69f525e3d5fef",
33+
),
34+
),
35+
(
36+
"demo-0.1.2-py2.py3-none-any.whl",
37+
(
38+
"53b4e10d2bfa81a4206221c4b87843d9",
39+
"55dde4e6828081de7a1e429f33180459c333d9da593db62a3d75a8f5e505dde1",
40+
"b35b9aab064e88fffe42309550ebe425907fb42ccb3b1d173b7d6b7509f38eac",
41+
),
42+
),
43+
),
44+
)
45+
def test_file_hashes_returns_proper_hashes_for_file(
46+
file: str, hashes: tuple[str, ...], distributions_dir: Path
47+
) -> None:
48+
manager = HashManager()
49+
manager.hash(distributions_dir / file)
50+
file_hashes = manager.hexdigest()
51+
assert file_hashes == hashes
52+
53+
54+
def test_file_hashes_returns_none_for_md5_with_fips(
55+
mocker: MockerFixture, distributions_dir: Path
56+
) -> None:
57+
# disable md5
58+
def fips_md5(*args: Any, **kwargs: Any) -> Any:
59+
raise ValueError("Disabled by FIPS")
60+
61+
mocker.patch("hashlib.md5", new=fips_md5)
62+
63+
manager = HashManager()
64+
manager.hash(distributions_dir / "demo-0.1.0.tar.gz")
65+
file_hashes = manager.hexdigest()
66+
67+
assert file_hashes.md5 is None
68+
69+
70+
def test_file_hashes_returns_none_for_blake2_with_fips(
71+
mocker: MockerFixture, distributions_dir: Path
72+
) -> None:
73+
# disable md5
74+
def fips_blake2b(*args: Any, **kwargs: Any) -> Any:
75+
raise ValueError("Disabled by FIPS")
76+
77+
mocker.patch("hashlib.blake2b", new=fips_blake2b)
78+
79+
manager = HashManager()
80+
manager.hash(distributions_dir / "demo-0.1.0.tar.gz")
81+
file_hashes = manager.hexdigest()
82+
83+
assert file_hashes.blake2_256 is None

0 commit comments

Comments
 (0)