Skip to content

Commit de8890f

Browse files
authored
gh-130149: cleanup refactorization of test_hmac.py (#131318)
New features: * refactor `hashlib_helper.requires_hashdigest` in prevision of a future `hashlib_helper.requires_builtin_hashdigest` for built-in hashes only * add `hashlib_helper.requires_openssl_hashdigest` to request OpenSSL hashes, assuming that `_hashlib` exists. Refactoring: * split hmac.copy() test by implementation * update how algorithms are discovered for RFC test cases * simplify how OpenSSL hash digests are requested * refactor hexdigest tests for RFC test vectors * typo fix: `assert_hmac_hexdigest_by_new` -> `assert_hmac_hexdigest_by_name` Improvements: * strengthen contract on `hmac_new_by_name` and `hmac_digest_by_name` * rename mixin classes to better match their responsibility
1 parent 85c04f8 commit de8890f

File tree

3 files changed

+256
-140
lines changed

3 files changed

+256
-140
lines changed

Lib/hmac.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ def __init__(self, key, msg=None, digestmod=''):
6565

6666
def _init_hmac(self, key, msg, digestmod):
6767
self._hmac = _hashopenssl.hmac_new(key, msg, digestmod=digestmod)
68+
self._inner = self._outer = None # because the slots are defined
6869
self.digest_size = self._hmac.digest_size
6970
self.block_size = self._hmac.block_size
7071

Lib/test/support/hashlib_helper.py

Lines changed: 65 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import functools
22
import hashlib
33
import unittest
4+
from test.support.import_helper import import_module
45

56
try:
67
import _hashlib
@@ -12,44 +13,81 @@ def requires_hashlib():
1213
return unittest.skipIf(_hashlib is None, "requires _hashlib")
1314

1415

16+
def _decorate_func_or_class(func_or_class, decorator_func):
17+
if not isinstance(func_or_class, type):
18+
return decorator_func(func_or_class)
19+
20+
decorated_class = func_or_class
21+
setUpClass = decorated_class.__dict__.get('setUpClass')
22+
if setUpClass is None:
23+
def setUpClass(cls):
24+
super(decorated_class, cls).setUpClass()
25+
setUpClass.__qualname__ = decorated_class.__qualname__ + '.setUpClass'
26+
setUpClass.__module__ = decorated_class.__module__
27+
else:
28+
setUpClass = setUpClass.__func__
29+
setUpClass = classmethod(decorator_func(setUpClass))
30+
decorated_class.setUpClass = setUpClass
31+
return decorated_class
32+
33+
1534
def requires_hashdigest(digestname, openssl=None, usedforsecurity=True):
16-
"""Decorator raising SkipTest if a hashing algorithm is not available
35+
"""Decorator raising SkipTest if a hashing algorithm is not available.
1736
18-
The hashing algorithm could be missing or blocked by a strict crypto
19-
policy.
37+
The hashing algorithm may be missing, blocked by a strict crypto policy,
38+
or Python may be configured with `--with-builtin-hashlib-hashes=no`.
2039
2140
If 'openssl' is True, then the decorator checks that OpenSSL provides
22-
the algorithm. Otherwise the check falls back to built-in
23-
implementations. The usedforsecurity flag is passed to the constructor.
41+
the algorithm. Otherwise the check falls back to (optional) built-in
42+
HACL* implementations.
43+
44+
The usedforsecurity flag is passed to the constructor but has no effect
45+
on HACL* implementations.
2446
47+
Examples of exceptions being suppressed:
2548
ValueError: [digital envelope routines: EVP_DigestInit_ex] disabled for FIPS
2649
ValueError: unsupported hash type md4
2750
"""
51+
if openssl and _hashlib is not None:
52+
def test_availability():
53+
_hashlib.new(digestname, usedforsecurity=usedforsecurity)
54+
else:
55+
def test_availability():
56+
hashlib.new(digestname, usedforsecurity=usedforsecurity)
57+
58+
def decorator_func(func):
59+
@functools.wraps(func)
60+
def wrapper(*args, **kwargs):
61+
try:
62+
test_availability()
63+
except ValueError as exc:
64+
msg = f"missing hash algorithm: {digestname!r}"
65+
raise unittest.SkipTest(msg) from exc
66+
return func(*args, **kwargs)
67+
return wrapper
68+
2869
def decorator(func_or_class):
29-
if isinstance(func_or_class, type):
30-
setUpClass = func_or_class.__dict__.get('setUpClass')
31-
if setUpClass is None:
32-
def setUpClass(cls):
33-
super(func_or_class, cls).setUpClass()
34-
setUpClass.__qualname__ = func_or_class.__qualname__ + '.setUpClass'
35-
setUpClass.__module__ = func_or_class.__module__
36-
else:
37-
setUpClass = setUpClass.__func__
38-
setUpClass = classmethod(decorator(setUpClass))
39-
func_or_class.setUpClass = setUpClass
40-
return func_or_class
41-
42-
@functools.wraps(func_or_class)
70+
return _decorate_func_or_class(func_or_class, decorator_func)
71+
return decorator
72+
73+
74+
def requires_openssl_hashdigest(digestname, *, usedforsecurity=True):
75+
"""Decorator raising SkipTest if an OpenSSL hashing algorithm is missing.
76+
77+
The hashing algorithm may be missing or blocked by a strict crypto policy.
78+
"""
79+
def decorator_func(func):
80+
@requires_hashlib()
81+
@functools.wraps(func)
4382
def wrapper(*args, **kwargs):
4483
try:
45-
if openssl and _hashlib is not None:
46-
_hashlib.new(digestname, usedforsecurity=usedforsecurity)
47-
else:
48-
hashlib.new(digestname, usedforsecurity=usedforsecurity)
84+
_hashlib.new(digestname, usedforsecurity=usedforsecurity)
4985
except ValueError:
50-
raise unittest.SkipTest(
51-
f"hash digest {digestname!r} is not available."
52-
)
53-
return func_or_class(*args, **kwargs)
86+
msg = f"missing OpenSSL hash algorithm: {digestname!r}"
87+
raise unittest.SkipTest(msg)
88+
return func(*args, **kwargs)
5489
return wrapper
90+
91+
def decorator(func_or_class):
92+
return _decorate_func_or_class(func_or_class, decorator_func)
5593
return decorator

0 commit comments

Comments
 (0)