1import functools
2import hashlib
3import unittest
4
5try:
6    import _hashlib
7except ImportError:
8    _hashlib = None
9
10
11def requires_hashdigest(digestname, openssl=None, usedforsecurity=True):
12    """Decorator raising SkipTest if a hashing algorithm is not available
13
14    The hashing algorithm could be missing or blocked by a strict crypto
15    policy.
16
17    If 'openssl' is True, then the decorator checks that OpenSSL provides
18    the algorithm. Otherwise the check falls back to built-in
19    implementations. The usedforsecurity flag is passed to the constructor.
20
21    ValueError: [digital envelope routines: EVP_DigestInit_ex] disabled for FIPS
22    ValueError: unsupported hash type md4
23    """
24    def decorator(func_or_class):
25        if isinstance(func_or_class, type):
26            setUpClass = func_or_class.__dict__.get('setUpClass')
27            if setUpClass is None:
28                def setUpClass(cls):
29                    super(func_or_class, cls).setUpClass()
30                setUpClass.__qualname__ = func_or_class.__qualname__ + '.setUpClass'
31                setUpClass.__module__ = func_or_class.__module__
32            else:
33                setUpClass = setUpClass.__func__
34            setUpClass = classmethod(decorator(setUpClass))
35            func_or_class.setUpClass = setUpClass
36            return func_or_class
37
38        @functools.wraps(func_or_class)
39        def wrapper(*args, **kwargs):
40            try:
41                if openssl and _hashlib is not None:
42                    _hashlib.new(digestname, usedforsecurity=usedforsecurity)
43                else:
44                    hashlib.new(digestname, usedforsecurity=usedforsecurity)
45            except ValueError:
46                raise unittest.SkipTest(
47                    f"hash digest '{digestname}' is not available."
48                )
49            return func_or_class(*args, **kwargs)
50        return wrapper
51    return decorator
52