Skip to content

Commit 833fc2e

Browse files
committed
update interface usage
1 parent 4ae51c7 commit 833fc2e

File tree

3 files changed

+53
-130
lines changed

3 files changed

+53
-130
lines changed

Lib/test/support/hashlib_helper.py

Lines changed: 41 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,6 @@
99
from types import MappingProxyType
1010

1111

12-
def try_import_module(module_name):
13-
"""Try to import a module and return None on failure."""
14-
try:
15-
return importlib.import_module(module_name)
16-
except ImportError:
17-
return None
18-
19-
2012
def _parse_fullname(fullname, *, strict=False):
2113
"""Parse a fully-qualified name <module_name>.<member_name>.
2214
@@ -252,71 +244,6 @@ def method_name(self, implementation):
252244
return self[implementation].member_name
253245

254246

255-
class HashInfo:
256-
"""Dataclass storing explicit hash constructor names.
257-
258-
- *builtin* is the fully-qualified name for the explicit HACL*
259-
hash constructor function, e.g., "_md5.md5".
260-
261-
- *openssl* is the name of the "_hashlib" module method for the explicit
262-
OpenSSL hash constructor function, e.g., "openssl_md5".
263-
264-
- *hashlib* is the name of the "hashlib" module method for the explicit
265-
hash constructor function, e.g., "md5".
266-
"""
267-
268-
def __init__(self, builtin, openssl=None, hashlib=None):
269-
assert isinstance(builtin, str), builtin
270-
assert len(builtin.split(".")) == 2, builtin
271-
272-
self.builtin = builtin
273-
self.builtin_module_name, self.builtin_method_name = (
274-
self.builtin.split(".", maxsplit=1)
275-
)
276-
277-
assert openssl is None or openssl.startswith("openssl_")
278-
self.openssl = self.openssl_method_name = openssl
279-
self.openssl_module_name = "_hashlib" if openssl else None
280-
281-
assert hashlib is None or isinstance(hashlib, str)
282-
self.hashlib = self.hashlib_method_name = hashlib
283-
self.hashlib_module_name = "hashlib" if hashlib else None
284-
285-
def module_name(self, implementation):
286-
match implementation:
287-
case "builtin":
288-
return self.builtin_module_name
289-
case "openssl":
290-
return self.openssl_module_name
291-
case "hashlib":
292-
return self.hashlib_module_name
293-
raise AssertionError(f"invalid implementation {implementation}")
294-
295-
def method_name(self, implementation):
296-
match implementation:
297-
case "builtin":
298-
return self.builtin_method_name
299-
case "openssl":
300-
return self.openssl_method_name
301-
case "hashlib":
302-
return self.hashlib_method_name
303-
raise AssertionError(f"invalid implementation {implementation}")
304-
305-
def fullname(self, implementation):
306-
"""Get the fully qualified name of a given implementation.
307-
308-
This returns a string of the form "MODULE_NAME.METHOD_NAME" or None
309-
if the hash function does not have a corresponding implementation.
310-
311-
*implementation* must be "builtin", "openssl" or "hashlib".
312-
"""
313-
module_name = self.module_name(implementation)
314-
method_name = self.method_name(implementation)
315-
if module_name is None or method_name is None:
316-
return None
317-
return f"{module_name}.{method_name}"
318-
319-
320247
class _HashInfo:
321248
"""Dataclass containing information for supported hash functions.
322249
@@ -443,6 +370,12 @@ def get_hash_func_info(name):
443370
return info.func
444371

445372

373+
def _iter_hash_func_info(excluded):
374+
for name, info in _HASHINFO_DATABASE.items():
375+
if name not in excluded:
376+
yield info.func
377+
378+
446379
# Mapping from canonical hash names to their explicit HACL* HMAC constructor.
447380
# There is currently no OpenSSL one-shot named function and there will likely
448381
# be none in the future.
@@ -511,12 +444,12 @@ def _ensure_wrapper_signature(wrapper, wrapped):
511444

512445

513446
def requires_openssl_hashlib():
514-
_hashlib = try_import_module("_hashlib")
447+
_hashlib = _import_module("_hashlib")
515448
return unittest.skipIf(_hashlib is None, "requires _hashlib")
516449

517450

518451
def requires_builtin_hmac():
519-
_hmac = try_import_module("_hmac")
452+
_hmac = _import_module("_hmac")
520453
return unittest.skipIf(_hmac is None, "requires _hmac")
521454

522455

@@ -544,7 +477,7 @@ def _hashlib_new(digestname, openssl, /, **kwargs):
544477
# exceptions as it should be unconditionally available.
545478
hashlib = importlib.import_module("hashlib")
546479
# re-import '_hashlib' in case it was mocked
547-
_hashlib = try_import_module("_hashlib")
480+
_hashlib = _import_module("_hashlib")
548481
module = _hashlib if openssl and _hashlib is not None else hashlib
549482
try:
550483
module.new(digestname, **kwargs)
@@ -665,29 +598,30 @@ def requires_openssl_hashdigest(digestname, *, usedforsecurity=True):
665598
)
666599

667600

668-
def requires_builtin_hashdigest(
669-
module_name, digestname, *, usedforsecurity=True
670-
):
671-
"""Decorator raising SkipTest if a HACL* hashing algorithm is missing.
672-
673-
- The *module_name* is the C extension module name based on HACL*.
674-
- The *digestname* is one of its member, e.g., 'md5'.
675-
"""
601+
def _make_requires_builtin_hashdigest_decorator(item, *, usedforsecurity=True):
602+
assert isinstance(item, _HashInfoItem), item
676603
return _make_requires_hashdigest_decorator(
677-
_builtin_hash, module_name, digestname, usedforsecurity=usedforsecurity
604+
_builtin_hash,
605+
item.module_name,
606+
item.member_name,
607+
usedforsecurity=usedforsecurity,
608+
)
609+
610+
611+
def requires_builtin_hashdigest(canonical_name, *, usedforsecurity=True):
612+
"""Decorator raising SkipTest if a HACL* hashing algorithm is missing."""
613+
info = get_hash_func_info(canonical_name)
614+
return _make_requires_builtin_hashdigest_decorator(
615+
info.builtin, usedforsecurity=usedforsecurity
678616
)
679617

680618

681619
def requires_builtin_hashes(*ignored, usedforsecurity=True):
682620
"""Decorator raising SkipTest if one HACL* hashing algorithm is missing."""
683621
return _chain_decorators((
684-
requires_builtin_hashdigest(
685-
api.builtin_module_name,
686-
api.builtin_method_name,
687-
usedforsecurity=usedforsecurity,
688-
)
689-
for name, api in _EXPLICIT_CONSTRUCTORS.items()
690-
if name not in ignored
622+
_make_requires_builtin_hashdigest_decorator(
623+
info.builtin, usedforsecurity=usedforsecurity
624+
) for info in _iter_hash_func_info(ignored)
691625
))
692626

693627

@@ -803,10 +737,10 @@ class BuiltinHashFunctionsTrait(HashFunctionsTrait):
803737

804738
def _find_constructor(self, digestname):
805739
self.is_valid_digest_name(digestname)
806-
info = _EXPLICIT_CONSTRUCTORS[digestname]
740+
info = get_hash_func_info(digestname)
807741
return _builtin_hash(
808-
info.builtin_module_name,
809-
info.builtin_method_name,
742+
info.builtin.module_name,
743+
info.builtin.member_name,
810744
usedforsecurity=self.usedforsecurity,
811745
)
812746

@@ -822,7 +756,7 @@ def find_gil_minsize(modules_names, default=2048):
822756
"""
823757
sizes = []
824758
for module_name in modules_names:
825-
module = try_import_module(module_name)
759+
module = _import_module(module_name)
826760
if module is not None:
827761
sizes.append(getattr(module, '_GIL_MINSIZE', default))
828762
return max(sizes, default=default)
@@ -833,7 +767,7 @@ def _block_openssl_hash_new(blocked_name):
833767
assert isinstance(blocked_name, str), blocked_name
834768

835769
# re-import '_hashlib' in case it was mocked
836-
if (_hashlib := try_import_module("_hashlib")) is None:
770+
if (_hashlib := _import_module("_hashlib")) is None:
837771
return contextlib.nullcontext()
838772

839773
@functools.wraps(wrapped := _hashlib.new)
@@ -852,7 +786,7 @@ def _block_openssl_hmac_new(blocked_name):
852786
assert isinstance(blocked_name, str), blocked_name
853787

854788
# re-import '_hashlib' in case it was mocked
855-
if (_hashlib := try_import_module("_hashlib")) is None:
789+
if (_hashlib := _import_module("_hashlib")) is None:
856790
return contextlib.nullcontext()
857791

858792
@functools.wraps(wrapped := _hashlib.hmac_new)
@@ -870,7 +804,7 @@ def _block_openssl_hmac_digest(blocked_name):
870804
assert isinstance(blocked_name, str), blocked_name
871805

872806
# re-import '_hashlib' in case it was mocked
873-
if (_hashlib := try_import_module("_hashlib")) is None:
807+
if (_hashlib := _import_module("_hashlib")) is None:
874808
return contextlib.nullcontext()
875809

876810
@functools.wraps(wrapped := _hashlib.hmac_digest)
@@ -900,7 +834,7 @@ def _block_builtin_hash_new(name):
900834
# so we need to block the possibility of importing it, but only
901835
# during the call to __get_builtin_constructor().
902836
get_builtin_constructor = getattr(hashlib, '__get_builtin_constructor')
903-
builtin_module_name = _EXPLICIT_CONSTRUCTORS[name].builtin_module_name
837+
builtin_module_name = get_hash_func_info(name).builtin.module_name
904838

905839
@functools.wraps(get_builtin_constructor)
906840
def get_builtin_constructor_mock(name):
@@ -920,7 +854,7 @@ def _block_builtin_hmac_new(blocked_name):
920854
assert isinstance(blocked_name, str), blocked_name
921855

922856
# re-import '_hmac' in case it was mocked
923-
if (_hmac := try_import_module("_hmac")) is None:
857+
if (_hmac := _import_module("_hmac")) is None:
924858
return contextlib.nullcontext()
925859

926860
@functools.wraps(wrapped := _hmac.new)
@@ -937,7 +871,7 @@ def _block_builtin_hmac_digest(blocked_name):
937871
assert isinstance(blocked_name, str), blocked_name
938872

939873
# re-import '_hmac' in case it was mocked
940-
if (_hmac := try_import_module("_hmac")) is None:
874+
if (_hmac := _import_module("_hmac")) is None:
941875
return contextlib.nullcontext()
942876

943877
@functools.wraps(wrapped := _hmac.compute_digest)
@@ -951,30 +885,19 @@ def _hmac_compute_digest(key, msg, digest):
951885

952886

953887
def _make_hash_constructor_blocker(name, dummy, implementation):
954-
info = _EXPLICIT_CONSTRUCTORS[name]
955-
module_name = info.module_name(implementation)
956-
method_name = info.method_name(implementation)
957-
if module_name is None or method_name is None:
888+
info = get_hash_func_info(name)[implementation]
889+
if (wrapped := info.import_member()) is None:
958890
# function shouldn't exist for this implementation
959891
return contextlib.nullcontext()
960-
961-
try:
962-
module = importlib.import_module(module_name)
963-
except ImportError:
964-
# module is already disabled
965-
return contextlib.nullcontext()
966-
967-
wrapped = getattr(module, method_name)
968892
wrapper = functools.wraps(wrapped)(dummy)
969893
_ensure_wrapper_signature(wrapper, wrapped)
970-
return unittest.mock.patch(info.fullname(implementation), wrapper)
894+
return unittest.mock.patch(info.fullname, wrapper)
971895

972896

973897
def _block_hashlib_hash_constructor(name):
974898
"""Block explicit public constructors."""
975899
def dummy(data=b'', *, usedforsecurity=True, string=None):
976900
raise ValueError(f"blocked explicit public hash name: {name}")
977-
978901
return _make_hash_constructor_blocker(name, dummy, 'hashlib')
979902

980903

@@ -1040,14 +963,14 @@ def block_algorithm(name, *, allow_openssl=False, allow_builtin=False):
1040963
# the OpenSSL implementation, except with usedforsecurity=False.
1041964
# However, blocking such functions also means blocking them
1042965
# so we again need to block them if we want to.
1043-
(_hashlib := try_import_module("_hashlib"))
966+
(_hashlib := _import_module("_hashlib"))
1044967
and _hashlib.get_fips_mode()
1045968
and not allow_openssl
1046969
) or (
1047970
# Without OpenSSL, hashlib.<name>() functions are aliases
1048971
# to built-in functions, so both of them must be blocked
1049972
# as the module may have been imported before the HACL ones.
1050-
not (_hashlib := try_import_module("_hashlib"))
973+
not (_hashlib := _import_module("_hashlib"))
1051974
and not allow_builtin
1052975
):
1053976
stack.enter_context(_block_hashlib_hash_constructor(name))

Lib/test/test_hmac.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1509,7 +1509,7 @@ def test_hmac_digest_overflow_error_openssl_only(self, size):
15091509
hmac = import_fresh_module("hmac", blocked=["_hmac"])
15101510
self.do_test_hmac_digest_overflow_error_switch_to_slow(hmac, size)
15111511

1512-
@hashlib_helper.requires_builtin_hashdigest("_md5", "md5")
1512+
@hashlib_helper.requires_builtin_hashdigest("md5")
15131513
@bigmemtest(size=_4G + 5, memuse=2, dry_run=False)
15141514
def test_hmac_digest_overflow_error_builtin_only(self, size):
15151515
hmac = import_fresh_module("hmac", blocked=["_hashlib"])

Lib/test/test_support.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -865,17 +865,17 @@ def try_import_attribute(self, fullname, default=None):
865865
return default
866866

867867
def fetch_hash_function(self, name, implementation):
868-
info = hashlib_helper.get_hash_info(name)
869-
match implementation:
870-
case "hashlib":
871-
assert info.hashlib is not None, info
872-
return getattr(self.hashlib, info.hashlib)
873-
case "openssl":
874-
try:
875-
return getattr(self._hashlib, info.openssl, None)
876-
except TypeError:
877-
return None
878-
fullname = info.fullname(implementation)
868+
info = hashlib_helper.get_hash_func_info(name)
869+
match hashlib_helper.Implementation(implementation):
870+
case hashlib_helper.Implementation.hashlib:
871+
method_name = info.hashlib.member_name
872+
assert isinstance(method_name, str), method_name
873+
return getattr(self.hashlib, method_name)
874+
case hashlib_helper.Implementation.openssl:
875+
method_name = info.openssl.member_name
876+
assert isinstance(method_name, str | None), method_name
877+
return getattr(self._hashlib, method_name or "", None)
878+
fullname = info[implementation].fullname
879879
return self.try_import_attribute(fullname)
880880

881881
def fetch_hmac_function(self, name):

0 commit comments

Comments
 (0)