99from 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-
2012def _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-
320247class _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
513446def 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
518451def 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
681619def 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
953887def _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
973897def _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 ))
0 commit comments