Skip to content

Commit 21c2bdd

Browse files
authored
python-3.13: cherry-pick proposed change to block blake2 (#36503)
With these changes, blake will not be available in FIPS mode unless usedforsecurity=False. Also see: - python/cpython#127301 - python/cpython#127298
1 parent 5f1b201 commit 21c2bdd

File tree

2 files changed

+288
-2
lines changed

2 files changed

+288
-2
lines changed

python-3.13.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.13
33
version: 3.13.1
4-
epoch: 2
4+
epoch: 3
55
description: "the Python programming language"
66
copyright:
77
- license: PSF-2.0
@@ -66,7 +66,7 @@ pipeline:
6666
6767
- uses: patch
6868
with:
69-
patches: gh-118224.patch
69+
patches: gh-118224.patch gh-127301.patch
7070

7171
- name: Configure
7272
runs: |

python-3.13/gh-127301.patch

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

0 commit comments

Comments
 (0)