@@ -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+ 
2642def  _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+ 
106313def  find_gil_minsize (modules_names , default = 2048 ):
107314    """Get the largest GIL_MINSIZE value for the given cryptographic modules. 
108315
0 commit comments