Skip to content

Commit c575006

Browse files
committed
fix python fips detection and blocking of blake2
- fixes chainguard-dev/internal-dev/#11886 - backports from #36503 (see python/cpython#127301) Signed-off-by: Dan Ryan <[email protected]>
1 parent 5514801 commit c575006

File tree

6 files changed

+807
-5
lines changed

6 files changed

+807
-5
lines changed

python-3.10.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package:
22
name: python-3.10
33
version: "3.10.17"
4-
epoch: 0
4+
epoch: 1
55
description: "the Python programming language"
66
copyright:
77
- license: PSF-2.0
@@ -57,7 +57,7 @@ pipeline:
5757
5858
- uses: patch
5959
with:
60-
patches: gh-118224.patch
60+
patches: gh-118224.patch gh-127301.patch
6161

6262
- name: Configure
6363
runs: |

python-3.10/gh-127301.patch

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
Upstream: https://github.com/python/cpython/pull/127301
2+
3+
diff --git a/Lib/hashlib.py b/Lib/hashlib.py
4+
index b546a3fd795..9362100fc70 100644
5+
--- a/Lib/hashlib.py
6+
+++ b/Lib/hashlib.py
7+
@@ -87,32 +87,32 @@ def __get_builtin_constructor(name):
8+
try:
9+
if name in {'SHA1', 'sha1'}:
10+
import _sha1
11+
- cache['SHA1'] = cache['sha1'] = _sha1.sha1
12+
+ cache['SHA1'] = cache['sha1'] = __get_wrapped_builtin(_sha1.sha1, name)
13+
elif name in {'MD5', 'md5'}:
14+
import _md5
15+
- cache['MD5'] = cache['md5'] = _md5.md5
16+
+ cache['MD5'] = cache['md5'] = __get_wrapped_builtin(_md5.md5, name)
17+
elif name in {'SHA256', 'sha256', 'SHA224', 'sha224'}:
18+
import _sha256
19+
- cache['SHA224'] = cache['sha224'] = _sha256.sha224
20+
- cache['SHA256'] = cache['sha256'] = _sha256.sha256
21+
+ cache['SHA224'] = cache['sha224'] = __get_wrapped_builtin(_sha256.sha224, name)
22+
+ cache['SHA256'] = cache['sha256'] = __get_wrapped_builtin(_sha256.sha256, name)
23+
elif name in {'SHA512', 'sha512', 'SHA384', 'sha384'}:
24+
import _sha512
25+
- cache['SHA384'] = cache['sha384'] = _sha512.sha384
26+
- cache['SHA512'] = cache['sha512'] = _sha512.sha512
27+
+ cache['SHA384'] = cache['sha384'] = __get_wrapped_builtin(_sha512.sha384, name)
28+
+ cache['SHA512'] = cache['sha512'] = __get_wrapped_builtin(_sha512.sha512, name)
29+
elif name in {'blake2b', 'blake2s'}:
30+
import _blake2
31+
- cache['blake2b'] = _blake2.blake2b
32+
- cache['blake2s'] = _blake2.blake2s
33+
+ cache['blake2b'] = __get_wrapped_builtin(_blake2.blake2b, name)
34+
+ cache['blake2s'] = __get_wrapped_builtin(_blake2.blake2s, name)
35+
elif name in {'sha3_224', 'sha3_256', 'sha3_384', 'sha3_512'}:
36+
import _sha3
37+
- cache['sha3_224'] = _sha3.sha3_224
38+
- cache['sha3_256'] = _sha3.sha3_256
39+
- cache['sha3_384'] = _sha3.sha3_384
40+
- cache['sha3_512'] = _sha3.sha3_512
41+
+ cache['sha3_224'] = __get_wrapped_builtin(_sha3.sha3_224, name)
42+
+ cache['sha3_256'] = __get_wrapped_builtin(_sha3.sha3_256, name)
43+
+ cache['sha3_384'] = __get_wrapped_builtin(_sha3.sha3_384, name)
44+
+ cache['sha3_512'] = __get_wrapped_builtin(_sha3.sha3_512, name)
45+
elif name in {'shake_128', 'shake_256'}:
46+
import _sha3
47+
- cache['shake_128'] = _sha3.shake_128
48+
- cache['shake_256'] = _sha3.shake_256
49+
+ cache['shake_128'] = __get_wrapped_builtin(_sha3.shake_128, name)
50+
+ cache['shake_256'] = __get_wrapped_builtin(_sha3.shake_256, name)
51+
except ImportError:
52+
pass # no extension module, this hash is unsupported.
53+
54+
@@ -161,9 +161,8 @@ def __hash_new(name, data=b'', **kwargs):
55+
except ValueError:
56+
# If the _hashlib module (OpenSSL) doesn't support the named
57+
# hash, try using our builtin implementations.
58+
- # This allows for SHA224/256 and SHA384/512 support even though
59+
- # the OpenSSL library prior to 0.9.8 doesn't provide them.
60+
- return __get_builtin_constructor(name)(data)
61+
+ # OpenSSL may not have been compiled to support everything.
62+
+ return __get_builtin_constructor(name)(data, **kwargs)
63+
64+
65+
try:
66+
@@ -172,6 +171,10 @@ def __hash_new(name, data=b'', **kwargs):
67+
__get_hash = __get_openssl_constructor
68+
algorithms_available = algorithms_available.union(
69+
_hashlib.openssl_md_meth_names)
70+
+ try:
71+
+ __openssl_fips_mode = _hashlib.get_fips_mode()
72+
+ except ValueError:
73+
+ __openssl_fips_mode = 0
74+
except ImportError:
75+
_hashlib = None
76+
new = __py_new
77+
diff --git a/Lib/test/_test_hashlib_fips.py b/Lib/test/_test_hashlib_fips.py
78+
new file mode 100644
79+
index 00000000000..92537245954
80+
--- /dev/null
81+
+++ b/Lib/test/_test_hashlib_fips.py
82+
@@ -0,0 +1,64 @@
83+
+# Test the hashlib module usedforsecurity wrappers under fips.
84+
+#
85+
+# Copyright (C) 2024 Dimitri John Ledkov ([email protected])
86+
+# Licensed to PSF under a Contributor Agreement.
87+
+#
88+
+
89+
+"""Primarily executed by test_hashlib.py. It can run stand alone by humans."""
90+
+
91+
+import os
92+
+import unittest
93+
+
94+
+OPENSSL_CONF_BACKUP = os.environ.get("OPENSSL_CONF")
95+
+
96+
+
97+
+class HashLibFIPSTestCase(unittest.TestCase):
98+
+ @classmethod
99+
+ def setUpClass(cls):
100+
+ # This openssl.cnf mocks FIPS mode without any digest
101+
+ # loaded. It means all digests must raise ValueError when
102+
+ # usedforsecurity=True via either openssl or builtin
103+
+ # constructors
104+
+ OPENSSL_CONF = os.path.join(os.path.dirname(__file__), "hashlibdata", "openssl.cnf")
105+
+ os.environ["OPENSSL_CONF"] = OPENSSL_CONF
106+
+ # Ensure hashlib is loading a fresh libcrypto with openssl
107+
+ # context affected by the above config file. Check if this can
108+
+ # be folded into test_hashlib.py, specifically if
109+
+ # import_fresh_module() results in a fresh library context
110+
+ import hashlib
111+
+
112+
+ def setUp(self):
113+
+ try:
114+
+ from _hashlib import get_fips_mode
115+
+ except ImportError:
116+
+ self.skipTest('_hashlib not available')
117+
+
118+
+ if get_fips_mode() != 1:
119+
+ self.skipTest('mocking fips mode failed')
120+
+
121+
+ @classmethod
122+
+ def tearDownClass(cls):
123+
+ if OPENSSL_CONF_BACKUP is not None:
124+
+ os.environ["OPENSSL_CONF"] = OPENSSL_CONF_BACKUP
125+
+ else:
126+
+ os.environ.pop("OPENSSL_CONF", None)
127+
+
128+
+ def test_algorithms_available(self):
129+
+ import hashlib
130+
+ self.assertTrue(set(hashlib.algorithms_guaranteed).
131+
+ issubset(hashlib.algorithms_available))
132+
+ # all available algorithms must be loadable, bpo-47101
133+
+ self.assertNotIn("undefined", hashlib.algorithms_available)
134+
+ for name in hashlib.algorithms_available:
135+
+ with self.subTest(name):
136+
+ digest = hashlib.new(name, usedforsecurity=False)
137+
+
138+
+ def test_usedforsecurity_true(self):
139+
+ import hashlib
140+
+ for name in hashlib.algorithms_available:
141+
+ with self.subTest(name):
142+
+ with self.assertRaises(ValueError):
143+
+ digest = hashlib.new(name, usedforsecurity=True)
144+
+
145+
+if __name__ == "__main__":
146+
+ unittest.main()
147+
diff --git a/Lib/test/hashlibdata/openssl.cnf b/Lib/test/hashlibdata/openssl.cnf
148+
new file mode 100644
149+
index 00000000000..9a936ddc5ef
150+
--- /dev/null
151+
+++ b/Lib/test/hashlibdata/openssl.cnf
152+
@@ -0,0 +1,19 @@
153+
+# Activate base provider only, with default properties fips=yes. It
154+
+# means that fips mode is on, and no digest implementations are
155+
+# available. Perfect for mock testing builtin FIPS wrappers.
156+
+
157+
+config_diagnostics = 1
158+
+openssl_conf = openssl_init
159+
+
160+
+[openssl_init]
161+
+providers = provider_sect
162+
+alg_section = algorithm_sect
163+
+
164+
+[provider_sect]
165+
+base = base_sect
166+
+
167+
+[base_sect]
168+
+activate = 1
169+
+
170+
+[algorithm_sect]
171+
+default_properties = fips=yes
172+
diff --git a/Lib/test/support/script_helper.py b/Lib/test/support/script_helper.py
173+
index 6d699c8486c..91f9050f022 100644
174+
--- a/Lib/test/support/script_helper.py
175+
+++ b/Lib/test/support/script_helper.py
176+
@@ -273,7 +273,14 @@ def make_zip_pkg(zip_dir, zip_basename, pkg_name, script_basename,
177+
return zip_name, os.path.join(zip_name, script_name_in_zip)
178+
179+
180+
-def run_test_script(script):
181+
+def run_test_script(script, **kwargs):
182+
+ """Run the file *script* in a child interpreter.
183+
+
184+
+ Keyword arguments are passed on to subprocess.run() within.
185+
+
186+
+ Asserts if the child exits non-zero. Prints child output after
187+
+ execution when run in verbose mode.
188+
+ """
189+
# use -u to try to get the full output if the test hangs or crash
190+
if support.verbose:
191+
def title(text):
192+
@@ -285,7 +292,7 @@ def title(text):
193+
# In verbose mode, the child process inherit stdout and stdout,
194+
# to see output in realtime and reduce the risk of losing output.
195+
args = [sys.executable, "-E", "-X", "faulthandler", "-u", script, "-v"]
196+
- proc = subprocess.run(args)
197+
+ proc = subprocess.run(args, **kwargs)
198+
print(title(f"{name} completed: exit code {proc.returncode}"),
199+
flush=True)
200+
if proc.returncode:
201+
diff --git a/Lib/test/test_hashlib.py b/Lib/test/test_hashlib.py
202+
index 9aa6c1f0a3b..1717954d523 100644
203+
--- a/Lib/test/test_hashlib.py
204+
+++ b/Lib/test/test_hashlib.py
205+
@@ -20,6 +20,7 @@
206+
from test import support
207+
from test.support import _4G, bigmemtest
208+
from test.support.import_helper import import_fresh_module
209+
+from test.support import script_helper
210+
from test.support import threading_helper
211+
from test.support import warnings_helper
212+
from http.client import HTTPException
213+
@@ -1130,6 +1131,18 @@ def test_normalized_name(self):
214+
self.assertNotIn("blake2b512", hashlib.algorithms_available)
215+
self.assertNotIn("sha3-512", hashlib.algorithms_available)
216+
217+
+ def test_builtins_in_openssl_fips_mode(self):
218+
+ try:
219+
+ from _hashlib import get_fips_mode
220+
+ except ImportError:
221+
+ self.skipTest('OpenSSL _hashlib not available')
222+
+ from test import _test_hashlib_fips
223+
+ child_test_path = _test_hashlib_fips.__file__
224+
+ env = os.environ.copy()
225+
+ # A config to mock FIPS mode, see _test_hashlib_fips.py.
226+
+ env["OPENSSL_CONF"] = os.path.join(os.path.dirname(__file__), "hashlibdata", "openssl.cnf")
227+
+ script_helper.run_test_script(child_test_path, env=env)
228+
+
229+
230+
if __name__ == "__main__":
231+
unittest.main()
232+
diff --git a/Makefile.pre.in b/Makefile.pre.in
233+
index fa99dd86c41..3d8b21e52b2 100644
234+
--- a/Makefile.pre.in
235+
+++ b/Makefile.pre.in
236+
@@ -1463,7 +1463,8 @@ TESTSUBDIRS= ctypes/test \
237+
test/capath test/cjkencodings \
238+
test/data test/decimaltestdata \
239+
test/dtracedata test/eintrdata \
240+
- test/encoded_modules test/imghdrdata \
241+
+ test/encoded_modules test/hashlibdata \
242+
+ test/imghdrdata \
243+
test/libregrtest test/sndhdrdata \
244+
test/subprocessdata test/support \
245+
test/test_asyncio \
246+
diff --git a/Misc/NEWS.d/next/Library/2024-11-26-16-31-40.gh-issue-127298.jqYJvn.rst b/Misc/NEWS.d/next/Library/2024-11-26-16-31-40.gh-issue-127298.jqYJvn.rst
247+
new file mode 100644
248+
index 00000000000..e555661a195
249+
--- /dev/null
250+
+++ b/Misc/NEWS.d/next/Library/2024-11-26-16-31-40.gh-issue-127298.jqYJvn.rst
251+
@@ -0,0 +1,8 @@
252+
+:mod:`hashlib`'s builtin hash implementations now check ``usedforsecurity=False``,
253+
+when the OpenSSL library default provider is in OpenSSL's FIPS mode. This helps
254+
+ensure that only US FIPS approved implementations are in use by default on systems
255+
+configured as such.
256+
+
257+
+This is only active when :mod:`hashlib` has been built with OpenSSL implementation
258+
+support and said OpenSSL library includes the FIPS mode feature. Not all variants
259+
+do, and OpenSSL is not a *required* build time dependency of ``hashlib``.

python-3.11.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ pipeline:
5757
5858
- uses: patch
5959
with:
60-
patches: gh-118224.patch
60+
patches: gh-118224.patch gh-127301.patch
6161

6262
- name: Configure
6363
runs: |

0 commit comments

Comments
 (0)