22import errno
33import importlib
44import itertools
5+ import inspect
56import io
67import logging
78import os
@@ -798,6 +799,7 @@ def test_linked_to_musl(self):
798799 # SuppressCrashReport
799800
800801
802+ @hashlib_helper .requires_builtin_hashes ()
801803class TestHashlibSupport (unittest .TestCase ):
802804
803805 @classmethod
@@ -806,11 +808,20 @@ def setUpClass(cls):
806808 cls .hashlib = import_helper .import_module ("hashlib" )
807809 cls .hmac = import_helper .import_module ("hmac" )
808810
809- # We required the extension modules to be present since blocking
810- # HACL* implementations while allowing OpenSSL ones would still
811- # result in failures.
811+ # All C extension modules must be present since blocking
812+ # the built-in implementation while allowing OpenSSL or vice-versa
813+ # may result in failures depending on the exposed built-in hashes .
812814 cls ._hashlib = import_helper .import_module ("_hashlib" )
813815 cls ._hmac = import_helper .import_module ("_hmac" )
816+ cls ._md5 = import_helper .import_module ("_md5" )
817+
818+ def skip_if_fips_mode (self ):
819+ if self ._hashlib .get_fips_mode ():
820+ self .skipTest ("disabled in FIPS mode" )
821+
822+ def skip_if_not_fips_mode (self ):
823+ if not self ._hashlib .get_fips_mode ():
824+ self .skipTest ("requires FIPS mode" )
814825
815826 def check_context (self , disabled = True ):
816827 if disabled :
@@ -831,25 +842,19 @@ def try_import_attribute(self, fullname, default=None):
831842 except TypeError :
832843 return default
833844
834- def validate_modules (self ):
835- if hasattr (hashlib_helper , 'hashlib' ):
836- self .assertIs (hashlib_helper .hashlib , self .hashlib )
837- if hasattr (hashlib_helper , 'hmac' ):
838- self .assertIs (hashlib_helper .hmac , self .hmac )
839-
840- def fetch_hash_function (self , name , typ ):
841- entry = hashlib_helper ._EXPLICIT_CONSTRUCTORS [name ]
842- match typ :
845+ def fetch_hash_function (self , name , implementation ):
846+ info = hashlib_helper .get_hash_info (name )
847+ match implementation :
843848 case "hashlib" :
844- assert entry .hashlib is not None , entry
845- return getattr (self .hashlib , entry .hashlib )
849+ assert info .hashlib is not None , info
850+ return getattr (self .hashlib , info .hashlib )
846851 case "openssl" :
847852 try :
848- return getattr (self ._hashlib , entry .openssl , None )
853+ return getattr (self ._hashlib , info .openssl , None )
849854 except TypeError :
850855 return None
851- case "builtin" :
852- return self .try_import_attribute (entry . fullname ( typ ) )
856+ fullname = info . fullname ( implementation )
857+ return self .try_import_attribute (fullname )
853858
854859 def fetch_hmac_function (self , name ):
855860 fullname = hashlib_helper ._EXPLICIT_HMAC_CONSTRUCTORS [name ]
@@ -914,16 +919,12 @@ def check_builtin_hmac(self, name, *, disabled=True):
914919 )
915920 def test_disable_hash (self , name , allow_openssl , allow_builtin ):
916921 # In FIPS mode, the function may be available but would still need
917- # to raise a ValueError. For simplicity, we don't test the helper
918- # when we're in FIPS mode.
919- if self ._hashlib .get_fips_mode ():
920- self .skipTest ("hash functions may still be blocked in FIPS mode" )
922+ # to raise a ValueError, so we will test the helper separately.
923+ self .skip_if_fips_mode ()
921924 flags = dict (allow_openssl = allow_openssl , allow_builtin = allow_builtin )
922- is_simple_disabled = not allow_builtin and not allow_openssl
925+ is_fully_disabled = not allow_builtin and not allow_openssl
923926
924927 with hashlib_helper .block_algorithm (name , ** flags ):
925- self .validate_modules ()
926-
927928 # OpenSSL's blake2s and blake2b are unknown names
928929 # when only the OpenSSL interface is available.
929930 if allow_openssl and not allow_builtin :
@@ -932,25 +933,104 @@ def test_disable_hash(self, name, allow_openssl, allow_builtin):
932933 else :
933934 name_for_hashlib_new = name
934935
935- with self .check_context (is_simple_disabled ):
936+ with self .check_context (is_fully_disabled ):
936937 _ = self .hashlib .new (name_for_hashlib_new )
937- with self .check_context (is_simple_disabled ):
938- _ = getattr (self .hashlib , name )(b"" )
938+
939+ # Since _hashlib is present, explicit blake2b/blake2s constructors
940+ # use the built-in implementation, while others (since we are not
941+ # in FIPS mode and since _hashlib exists) use the OpenSSL function.
942+ with self .check_context (is_fully_disabled ):
943+ _ = getattr (self .hashlib , name )()
939944
940945 self .check_openssl_hash (name , disabled = not allow_openssl )
941946 self .check_builtin_hash (name , disabled = not allow_builtin )
942947
943948 if name not in hashlib_helper .NON_HMAC_DIGEST_NAMES :
944- with self .check_context (is_simple_disabled ):
949+ with self .check_context (is_fully_disabled ):
945950 _ = self .hmac .new (b"" , b"" , name )
946- with self .check_context (is_simple_disabled ):
951+ with self .check_context (is_fully_disabled ):
947952 _ = self .hmac .HMAC (b"" , b"" , name )
948- with self .check_context (is_simple_disabled ):
953+ with self .check_context (is_fully_disabled ):
949954 _ = self .hmac .digest (b"" , b"" , name )
950955
951956 self .check_openssl_hmac (name , disabled = not allow_openssl )
952957 self .check_builtin_hmac (name , disabled = not allow_builtin )
953958
959+ @hashlib_helper .block_algorithm ("md5" )
960+ def test_disable_hash_md5_in_fips_mode (self ):
961+ self .skip_if_not_fips_mode ()
962+
963+ self .assertRaises (ValueError , self .hashlib .new , "md5" )
964+ self .assertRaises (ValueError , self ._hashlib .new , "md5" )
965+ self .assertRaises (ValueError , self .hashlib .md5 )
966+ self .assertRaises (ValueError , self ._hashlib .openssl_md5 )
967+
968+ kwargs = dict (usedforsecurity = True )
969+ self .assertRaises (ValueError , self .hashlib .new , "md5" , ** kwargs )
970+ self .assertRaises (ValueError , self ._hashlib .new , "md5" , ** kwargs )
971+ self .assertRaises (ValueError , self .hashlib .md5 , ** kwargs )
972+ self .assertRaises (ValueError , self ._hashlib .openssl_md5 , ** kwargs )
973+
974+ @hashlib_helper .block_algorithm ("md5" , allow_openssl = True )
975+ def test_disable_hash_md5_in_fips_mode_allow_openssl (self ):
976+ self .skip_if_not_fips_mode ()
977+ # Allow the OpenSSL interface to be used but not the HACL* one.
978+ # hashlib.new("md5") is dispatched to hashlib.openssl_md5()
979+ self .assertRaises (ValueError , self .hashlib .new , "md5" )
980+ # dispatched to hashlib.openssl_md5() in FIPS mode
981+ h2 = self .hashlib .new ("md5" , usedforsecurity = False )
982+ self .assertIsInstance (h2 , self ._hashlib .HASH )
983+
984+ # block_algorithm() does not mock hashlib.md5 and _hashlib.openssl_md5
985+ self .assertNotHasAttr (self .hashlib .md5 , "__wrapped__" )
986+ self .assertNotHasAttr (self ._hashlib .openssl_md5 , "__wrapped__" )
987+
988+ hashlib_md5 = inspect .unwrap (self .hashlib .md5 )
989+ self .assertIs (hashlib_md5 , self ._hashlib .openssl_md5 )
990+ self .assertRaises (ValueError , self .hashlib .md5 )
991+ # allow MD5 to be used in FIPS mode if usedforsecurity=False
992+ h3 = self .hashlib .md5 (usedforsecurity = False )
993+ self .assertIsInstance (h3 , self ._hashlib .HASH )
994+
995+ @hashlib_helper .block_algorithm ("md5" , allow_builtin = True )
996+ def test_disable_hash_md5_in_fips_mode_allow_builtin (self ):
997+ self .skip_if_not_fips_mode ()
998+ # Allow the HACL* interface to be used but not the OpenSSL one.
999+ h1 = self .hashlib .new ("md5" ) # dispatched to _md5.md5()
1000+ self .assertNotIsInstance (h1 , self ._hashlib .HASH )
1001+ h2 = self .hashlib .new ("md5" , usedforsecurity = False )
1002+ self .assertIsInstance (h2 , type (h1 ))
1003+
1004+ # block_algorithm() mocks hashlib.md5 and _hashlib.openssl_md5
1005+ self .assertHasAttr (self .hashlib .md5 , "__wrapped__" )
1006+ self .assertHasAttr (self ._hashlib .openssl_md5 , "__wrapped__" )
1007+
1008+ hashlib_md5 = inspect .unwrap (self .hashlib .md5 )
1009+ openssl_md5 = inspect .unwrap (self ._hashlib .openssl_md5 )
1010+ self .assertIs (hashlib_md5 , openssl_md5 )
1011+ self .assertRaises (ValueError , self .hashlib .md5 )
1012+ self .assertRaises (ValueError , self .hashlib .md5 ,
1013+ usedforsecurity = False )
1014+
1015+ @hashlib_helper .block_algorithm ("md5" ,
1016+ allow_openssl = True ,
1017+ allow_builtin = True )
1018+ def test_disable_hash_md5_in_fips_mode_allow_all (self ):
1019+ self .skip_if_not_fips_mode ()
1020+ # hashlib.new() isn't blocked as it falls back to _md5.md5
1021+ self .assertIsInstance (self .hashlib .new ("md5" ), self ._md5 .MD5Type )
1022+ self .assertRaises (ValueError , self ._hashlib .new , "md5" )
1023+ h = self ._hashlib .new ("md5" , usedforsecurity = False )
1024+ self .assertIsInstance (h , self ._hashlib .HASH )
1025+
1026+ self .assertNotHasAttr (self .hashlib .md5 , "__wrapped__" )
1027+ self .assertNotHasAttr (self ._hashlib .openssl_md5 , "__wrapped__" )
1028+
1029+ self .assertIs (self .hashlib .md5 , self ._hashlib .openssl_md5 )
1030+ self .assertRaises (ValueError , self .hashlib .md5 )
1031+ h = self .hashlib .md5 (usedforsecurity = False )
1032+ self .assertIsInstance (h , self ._hashlib .HASH )
1033+
9541034
9551035if __name__ == '__main__' :
9561036 unittest .main ()
0 commit comments