Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 73d71a4

Browse filesBrowse files
authored
gh-132388: test HACL* and OpenSSL hash functions in pure Python HMAC (#134051)
1 parent 1566c34 commit 73d71a4
Copy full SHA for 73d71a4

File tree

2 files changed

+308
-67
lines changed
Filter options

2 files changed

+308
-67
lines changed

‎Lib/test/support/hashlib_helper.py

Copy file name to clipboardExpand all lines: Lib/test/support/hashlib_helper.py
+214-7Lines changed: 214 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,22 @@ def requires_builtin_hmac():
2323
return unittest.skipIf(_hmac is None, "requires _hmac")
2424

2525

26+
def _missing_hash(digestname, implementation=None, *, exc=None):
27+
parts = ["missing", implementation, f"hash algorithm: {digestname!r}"]
28+
msg = " ".join(filter(None, parts))
29+
raise unittest.SkipTest(msg) from exc
30+
31+
32+
def _openssl_availabillity(digestname, *, usedforsecurity):
33+
try:
34+
_hashlib.new(digestname, usedforsecurity=usedforsecurity)
35+
except AttributeError:
36+
assert _hashlib is None
37+
_missing_hash(digestname, "OpenSSL")
38+
except ValueError as exc:
39+
_missing_hash(digestname, "OpenSSL", exc=exc)
40+
41+
2642
def _decorate_func_or_class(func_or_class, decorator_func):
2743
if not isinstance(func_or_class, type):
2844
return decorator_func(func_or_class)
@@ -71,8 +87,7 @@ def wrapper(*args, **kwargs):
7187
try:
7288
test_availability()
7389
except ValueError as exc:
74-
msg = f"missing hash algorithm: {digestname!r}"
75-
raise unittest.SkipTest(msg) from exc
90+
_missing_hash(digestname, exc=exc)
7691
return func(*args, **kwargs)
7792
return wrapper
7893

@@ -87,14 +102,44 @@ def requires_openssl_hashdigest(digestname, *, usedforsecurity=True):
87102
The hashing algorithm may be missing or blocked by a strict crypto policy.
88103
"""
89104
def decorator_func(func):
90-
@requires_hashlib()
105+
@requires_hashlib() # avoid checking at each call
91106
@functools.wraps(func)
92107
def wrapper(*args, **kwargs):
108+
_openssl_availabillity(digestname, usedforsecurity=usedforsecurity)
109+
return func(*args, **kwargs)
110+
return wrapper
111+
112+
def decorator(func_or_class):
113+
return _decorate_func_or_class(func_or_class, decorator_func)
114+
return decorator
115+
116+
117+
def find_openssl_hashdigest_constructor(digestname, *, usedforsecurity=True):
118+
"""Find the OpenSSL hash function constructor by its name."""
119+
assert isinstance(digestname, str), digestname
120+
_openssl_availabillity(digestname, usedforsecurity=usedforsecurity)
121+
# This returns a function of the form _hashlib.openssl_<name> and
122+
# not a lambda function as it is rejected by _hashlib.hmac_new().
123+
return getattr(_hashlib, f"openssl_{digestname}")
124+
125+
126+
def requires_builtin_hashdigest(
127+
module_name, digestname, *, usedforsecurity=True
128+
):
129+
"""Decorator raising SkipTest if a HACL* hashing algorithm is missing.
130+
131+
- The *module_name* is the C extension module name based on HACL*.
132+
- The *digestname* is one of its member, e.g., 'md5'.
133+
"""
134+
def decorator_func(func):
135+
@functools.wraps(func)
136+
def wrapper(*args, **kwargs):
137+
module = import_module(module_name)
93138
try:
94-
_hashlib.new(digestname, usedforsecurity=usedforsecurity)
95-
except ValueError:
96-
msg = f"missing OpenSSL hash algorithm: {digestname!r}"
97-
raise unittest.SkipTest(msg)
139+
getattr(module, digestname)
140+
except AttributeError:
141+
fullname = f'{module_name}.{digestname}'
142+
_missing_hash(fullname, implementation="HACL")
98143
return func(*args, **kwargs)
99144
return wrapper
100145

@@ -103,6 +148,168 @@ def decorator(func_or_class):
103148
return decorator
104149

105150

151+
def find_builtin_hashdigest_constructor(
152+
module_name, digestname, *, usedforsecurity=True
153+
):
154+
"""Find the HACL* hash function constructor.
155+
156+
- The *module_name* is the C extension module name based on HACL*.
157+
- The *digestname* is one of its member, e.g., 'md5'.
158+
"""
159+
module = import_module(module_name)
160+
try:
161+
constructor = getattr(module, digestname)
162+
constructor(b'', usedforsecurity=usedforsecurity)
163+
except (AttributeError, TypeError, ValueError):
164+
_missing_hash(f'{module_name}.{digestname}', implementation="HACL")
165+
return constructor
166+
167+
168+
class HashFunctionsTrait:
169+
"""Mixin trait class containing hash functions.
170+
171+
This class is assumed to have all unitest.TestCase methods but should
172+
not directly inherit from it to prevent the test suite being run on it.
173+
174+
Subclasses should implement the hash functions by returning an object
175+
that can be recognized as a valid digestmod parameter for both hashlib
176+
and HMAC. In particular, it cannot be a lambda function as it will not
177+
be recognized by hashlib (it will still be accepted by the pure Python
178+
implementation of HMAC).
179+
"""
180+
181+
ALGORITHMS = [
182+
'md5', 'sha1',
183+
'sha224', 'sha256', 'sha384', 'sha512',
184+
'sha3_224', 'sha3_256', 'sha3_384', 'sha3_512',
185+
]
186+
187+
# Default 'usedforsecurity' to use when looking up a hash function.
188+
usedforsecurity = True
189+
190+
def _find_constructor(self, name):
191+
# By default, a missing algorithm skips the test that uses it.
192+
self.assertIn(name, self.ALGORITHMS)
193+
self.skipTest(f"missing hash function: {name}")
194+
195+
@property
196+
def md5(self):
197+
return self._find_constructor("md5")
198+
199+
@property
200+
def sha1(self):
201+
return self._find_constructor("sha1")
202+
203+
@property
204+
def sha224(self):
205+
return self._find_constructor("sha224")
206+
207+
@property
208+
def sha256(self):
209+
return self._find_constructor("sha256")
210+
211+
@property
212+
def sha384(self):
213+
return self._find_constructor("sha384")
214+
215+
@property
216+
def sha512(self):
217+
return self._find_constructor("sha512")
218+
219+
@property
220+
def sha3_224(self):
221+
return self._find_constructor("sha3_224")
222+
223+
@property
224+
def sha3_256(self):
225+
return self._find_constructor("sha3_256")
226+
227+
@property
228+
def sha3_384(self):
229+
return self._find_constructor("sha3_384")
230+
231+
@property
232+
def sha3_512(self):
233+
return self._find_constructor("sha3_512")
234+
235+
236+
class NamedHashFunctionsTrait(HashFunctionsTrait):
237+
"""Trait containing named hash functions.
238+
239+
Hash functions are available if and only if they are available in hashlib.
240+
"""
241+
242+
def _find_constructor(self, name):
243+
self.assertIn(name, self.ALGORITHMS)
244+
return name
245+
246+
247+
class OpenSSLHashFunctionsTrait(HashFunctionsTrait):
248+
"""Trait containing OpenSSL hash functions.
249+
250+
Hash functions are available if and only if they are available in _hashlib.
251+
"""
252+
253+
def _find_constructor(self, name):
254+
self.assertIn(name, self.ALGORITHMS)
255+
return find_openssl_hashdigest_constructor(
256+
name, usedforsecurity=self.usedforsecurity
257+
)
258+
259+
260+
class BuiltinHashFunctionsTrait(HashFunctionsTrait):
261+
"""Trait containing HACL* hash functions.
262+
263+
Hash functions are available if and only if they are available in C.
264+
In particular, HACL* HMAC-MD5 may be available even though HACL* md5
265+
is not since the former is unconditionally built.
266+
"""
267+
268+
def _find_constructor_in(self, module, name):
269+
self.assertIn(name, self.ALGORITHMS)
270+
return find_builtin_hashdigest_constructor(module, name)
271+
272+
@property
273+
def md5(self):
274+
return self._find_constructor_in("_md5", "md5")
275+
276+
@property
277+
def sha1(self):
278+
return self._find_constructor_in("_sha1", "sha1")
279+
280+
@property
281+
def sha224(self):
282+
return self._find_constructor_in("_sha2", "sha224")
283+
284+
@property
285+
def sha256(self):
286+
return self._find_constructor_in("_sha2", "sha256")
287+
288+
@property
289+
def sha384(self):
290+
return self._find_constructor_in("_sha2", "sha384")
291+
292+
@property
293+
def sha512(self):
294+
return self._find_constructor_in("_sha2", "sha512")
295+
296+
@property
297+
def sha3_224(self):
298+
return self._find_constructor_in("_sha3", "sha3_224")
299+
300+
@property
301+
def sha3_256(self):
302+
return self._find_constructor_in("_sha3","sha3_256")
303+
304+
@property
305+
def sha3_384(self):
306+
return self._find_constructor_in("_sha3","sha3_384")
307+
308+
@property
309+
def sha3_512(self):
310+
return self._find_constructor_in("_sha3","sha3_512")
311+
312+
106313
def find_gil_minsize(modules_names, default=2048):
107314
"""Get the largest GIL_MINSIZE value for the given cryptographic modules.
108315

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.