@@ -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