2025-07-13 10:58:15 +02:00
|
|
|
import contextlib
|
2020-04-29 09:11:29 +08:00
|
|
|
import functools
|
|
|
|
|
import hashlib
|
2025-04-28 00:20:15 +02:00
|
|
|
import importlib
|
2025-07-13 10:58:15 +02:00
|
|
|
import inspect
|
2020-04-29 09:11:29 +08:00
|
|
|
import unittest
|
2025-07-13 10:58:15 +02:00
|
|
|
import unittest.mock
|
|
|
|
|
from collections import namedtuple
|
2025-03-17 11:10:03 +01:00
|
|
|
from test.support.import_helper import import_module
|
2025-07-13 10:58:15 +02:00
|
|
|
from types import MappingProxyType
|
2020-04-29 09:11:29 +08:00
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
import _hashlib
|
|
|
|
|
except ImportError:
|
|
|
|
|
_hashlib = None
|
|
|
|
|
|
2025-04-04 19:04:00 +02:00
|
|
|
try:
|
|
|
|
|
import _hmac
|
|
|
|
|
except ImportError:
|
|
|
|
|
_hmac = None
|
|
|
|
|
|
2020-04-29 09:11:29 +08:00
|
|
|
|
2025-07-13 10:58:15 +02:00
|
|
|
CANONICAL_DIGEST_NAMES = frozenset((
|
|
|
|
|
'md5', 'sha1',
|
|
|
|
|
'sha224', 'sha256', 'sha384', 'sha512',
|
|
|
|
|
'sha3_224', 'sha3_256', 'sha3_384', 'sha3_512',
|
|
|
|
|
'shake_128', 'shake_256',
|
|
|
|
|
'blake2s', 'blake2b',
|
|
|
|
|
))
|
|
|
|
|
|
|
|
|
|
NON_HMAC_DIGEST_NAMES = frozenset({
|
|
|
|
|
'shake_128', 'shake_256',
|
|
|
|
|
'blake2s', 'blake2b',
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class HashAPI(namedtuple("HashAPI", "builtin openssl hashlib")):
|
|
|
|
|
|
|
|
|
|
def fullname(self, typ):
|
|
|
|
|
match typ:
|
|
|
|
|
case "builtin":
|
|
|
|
|
return self.builtin
|
|
|
|
|
case "openssl":
|
|
|
|
|
return f"_hashlib.{self.openssl}" if self.openssl else None
|
|
|
|
|
case "hashlib":
|
|
|
|
|
return f"hashlib.{self.hashlib}" if self.hashlib else None
|
|
|
|
|
case _:
|
|
|
|
|
raise AssertionError(f"unknown type: {typ}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Mapping from a "canonical" name to a pair (HACL*, _hashlib.*, hashlib.*)
|
|
|
|
|
# constructors. If the constructor name is None, then this means that the
|
|
|
|
|
# algorithm can only be used by the "agile" new() interfaces.
|
|
|
|
|
_EXPLICIT_CONSTRUCTORS = MappingProxyType({
|
|
|
|
|
"md5": HashAPI("_md5.md5", "openssl_md5", "md5"),
|
|
|
|
|
"sha1": HashAPI("_sha1.sha1", "openssl_sha1", "sha1"),
|
|
|
|
|
"sha224": HashAPI("_sha2.sha224", "openssl_sha224", "sha224"),
|
|
|
|
|
"sha256": HashAPI("_sha2.sha256", "openssl_sha256", "sha256"),
|
|
|
|
|
"sha384": HashAPI("_sha2.sha384", "openssl_sha384", "sha384"),
|
|
|
|
|
"sha512": HashAPI("_sha2.sha512", "openssl_sha512", "sha512"),
|
|
|
|
|
"sha3_224": HashAPI("_sha3.sha3_224", "openssl_sha3_224", "sha3_224"),
|
|
|
|
|
"sha3_256": HashAPI("_sha3.sha3_256", "openssl_sha3_256", "sha3_256"),
|
|
|
|
|
"sha3_384": HashAPI("_sha3.sha3_384", "openssl_sha3_384", "sha3_384"),
|
|
|
|
|
"sha3_512": HashAPI("_sha3.sha3_512", "openssl_sha3_512", "sha3_512"),
|
|
|
|
|
"shake_128": HashAPI("_sha3.shake_128", "openssl_shake_128", "shake_128"),
|
|
|
|
|
"shake_256": HashAPI("_sha3.shake_256", "openssl_shake_256", "shake_256"),
|
|
|
|
|
"blake2s": HashAPI("_blake2.blake2s", None, "blake2s"),
|
|
|
|
|
"blake2b": HashAPI("_blake2.blake2b", None, "blake2b"),
|
|
|
|
|
})
|
|
|
|
|
assert _EXPLICIT_CONSTRUCTORS.keys() == CANONICAL_DIGEST_NAMES
|
|
|
|
|
|
|
|
|
|
_EXPLICIT_HMAC_CONSTRUCTORS = {
|
|
|
|
|
name: f'_hmac.compute_{name}' for name in (
|
|
|
|
|
'md5', 'sha1',
|
|
|
|
|
'sha224', 'sha256', 'sha384', 'sha512',
|
|
|
|
|
'sha3_224', 'sha3_256', 'sha3_384', 'sha3_512',
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
_EXPLICIT_HMAC_CONSTRUCTORS['shake_128'] = None
|
|
|
|
|
_EXPLICIT_HMAC_CONSTRUCTORS['shake_256'] = None
|
|
|
|
|
# Strictly speaking, HMAC-BLAKE is meaningless as BLAKE2 is already a
|
|
|
|
|
# keyed hash function. However, as it's exposed by HACL*, we test it.
|
|
|
|
|
_EXPLICIT_HMAC_CONSTRUCTORS['blake2s'] = '_hmac.compute_blake2s_32'
|
|
|
|
|
_EXPLICIT_HMAC_CONSTRUCTORS['blake2b'] = '_hmac.compute_blake2b_32'
|
|
|
|
|
_EXPLICIT_HMAC_CONSTRUCTORS = MappingProxyType(_EXPLICIT_HMAC_CONSTRUCTORS)
|
|
|
|
|
assert _EXPLICIT_HMAC_CONSTRUCTORS.keys() == CANONICAL_DIGEST_NAMES
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _ensure_wrapper_signature(wrapper, wrapped):
|
|
|
|
|
"""Ensure that a wrapper has the same signature as the wrapped function.
|
|
|
|
|
|
|
|
|
|
This is used to guarantee that a TypeError raised due to a bad API call
|
|
|
|
|
is raised consistently (using variadic signatures would hide such errors).
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
wrapped_sig = inspect.signature(wrapped)
|
|
|
|
|
except ValueError: # built-in signature cannot be found
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
wrapper_sig = inspect.signature(wrapper)
|
|
|
|
|
if wrapped_sig != wrapper_sig:
|
|
|
|
|
fullname = f"{wrapped.__module__}.{wrapped.__qualname__}"
|
|
|
|
|
raise AssertionError(
|
|
|
|
|
f"signature for {fullname}() is incorrect:\n"
|
|
|
|
|
f" expect: {wrapped_sig}\n"
|
|
|
|
|
f" actual: {wrapper_sig}"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2025-03-03 11:22:05 +01:00
|
|
|
def requires_hashlib():
|
|
|
|
|
return unittest.skipIf(_hashlib is None, "requires _hashlib")
|
|
|
|
|
|
|
|
|
|
|
2025-04-04 19:04:00 +02:00
|
|
|
def requires_builtin_hmac():
|
|
|
|
|
return unittest.skipIf(_hmac is None, "requires _hmac")
|
|
|
|
|
|
|
|
|
|
|
2025-05-16 14:00:01 +02:00
|
|
|
def _missing_hash(digestname, implementation=None, *, exc=None):
|
|
|
|
|
parts = ["missing", implementation, f"hash algorithm: {digestname!r}"]
|
|
|
|
|
msg = " ".join(filter(None, parts))
|
|
|
|
|
raise unittest.SkipTest(msg) from exc
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _openssl_availabillity(digestname, *, usedforsecurity):
|
2025-07-13 10:58:15 +02:00
|
|
|
assert isinstance(digestname, str), digestname
|
2025-05-16 14:00:01 +02:00
|
|
|
try:
|
|
|
|
|
_hashlib.new(digestname, usedforsecurity=usedforsecurity)
|
|
|
|
|
except AttributeError:
|
|
|
|
|
assert _hashlib is None
|
|
|
|
|
_missing_hash(digestname, "OpenSSL")
|
|
|
|
|
except ValueError as exc:
|
|
|
|
|
_missing_hash(digestname, "OpenSSL", exc=exc)
|
|
|
|
|
|
|
|
|
|
|
2025-03-17 11:10:03 +01:00
|
|
|
def _decorate_func_or_class(func_or_class, decorator_func):
|
|
|
|
|
if not isinstance(func_or_class, type):
|
|
|
|
|
return decorator_func(func_or_class)
|
|
|
|
|
|
|
|
|
|
decorated_class = func_or_class
|
|
|
|
|
setUpClass = decorated_class.__dict__.get('setUpClass')
|
|
|
|
|
if setUpClass is None:
|
|
|
|
|
def setUpClass(cls):
|
|
|
|
|
super(decorated_class, cls).setUpClass()
|
|
|
|
|
setUpClass.__qualname__ = decorated_class.__qualname__ + '.setUpClass'
|
|
|
|
|
setUpClass.__module__ = decorated_class.__module__
|
|
|
|
|
else:
|
|
|
|
|
setUpClass = setUpClass.__func__
|
|
|
|
|
setUpClass = classmethod(decorator_func(setUpClass))
|
|
|
|
|
decorated_class.setUpClass = setUpClass
|
|
|
|
|
return decorated_class
|
|
|
|
|
|
|
|
|
|
|
2020-04-29 09:11:29 +08:00
|
|
|
def requires_hashdigest(digestname, openssl=None, usedforsecurity=True):
|
2025-03-17 11:10:03 +01:00
|
|
|
"""Decorator raising SkipTest if a hashing algorithm is not available.
|
2020-04-29 09:11:29 +08:00
|
|
|
|
2025-03-17 11:10:03 +01:00
|
|
|
The hashing algorithm may be missing, blocked by a strict crypto policy,
|
|
|
|
|
or Python may be configured with `--with-builtin-hashlib-hashes=no`.
|
2020-04-29 09:11:29 +08:00
|
|
|
|
|
|
|
|
If 'openssl' is True, then the decorator checks that OpenSSL provides
|
2025-03-17 11:10:03 +01:00
|
|
|
the algorithm. Otherwise the check falls back to (optional) built-in
|
|
|
|
|
HACL* implementations.
|
|
|
|
|
|
|
|
|
|
The usedforsecurity flag is passed to the constructor but has no effect
|
|
|
|
|
on HACL* implementations.
|
2020-04-29 09:11:29 +08:00
|
|
|
|
2025-03-17 11:10:03 +01:00
|
|
|
Examples of exceptions being suppressed:
|
2020-04-29 09:11:29 +08:00
|
|
|
ValueError: [digital envelope routines: EVP_DigestInit_ex] disabled for FIPS
|
|
|
|
|
ValueError: unsupported hash type md4
|
|
|
|
|
"""
|
2025-07-13 10:58:15 +02:00
|
|
|
assert isinstance(digestname, str), digestname
|
2025-03-17 11:10:03 +01:00
|
|
|
if openssl and _hashlib is not None:
|
|
|
|
|
def test_availability():
|
|
|
|
|
_hashlib.new(digestname, usedforsecurity=usedforsecurity)
|
|
|
|
|
else:
|
|
|
|
|
def test_availability():
|
|
|
|
|
hashlib.new(digestname, usedforsecurity=usedforsecurity)
|
|
|
|
|
|
|
|
|
|
def decorator_func(func):
|
|
|
|
|
@functools.wraps(func)
|
|
|
|
|
def wrapper(*args, **kwargs):
|
|
|
|
|
try:
|
|
|
|
|
test_availability()
|
|
|
|
|
except ValueError as exc:
|
2025-05-16 14:00:01 +02:00
|
|
|
_missing_hash(digestname, exc=exc)
|
2025-03-17 11:10:03 +01:00
|
|
|
return func(*args, **kwargs)
|
|
|
|
|
return wrapper
|
|
|
|
|
|
2021-09-04 23:42:36 +03:00
|
|
|
def decorator(func_or_class):
|
2025-03-17 11:10:03 +01:00
|
|
|
return _decorate_func_or_class(func_or_class, decorator_func)
|
|
|
|
|
return decorator
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def requires_openssl_hashdigest(digestname, *, usedforsecurity=True):
|
|
|
|
|
"""Decorator raising SkipTest if an OpenSSL hashing algorithm is missing.
|
|
|
|
|
|
|
|
|
|
The hashing algorithm may be missing or blocked by a strict crypto policy.
|
|
|
|
|
"""
|
2025-07-13 10:58:15 +02:00
|
|
|
assert isinstance(digestname, str), digestname
|
2025-03-17 11:10:03 +01:00
|
|
|
def decorator_func(func):
|
2025-05-16 14:00:01 +02:00
|
|
|
@requires_hashlib() # avoid checking at each call
|
2025-03-17 11:10:03 +01:00
|
|
|
@functools.wraps(func)
|
2020-04-29 09:11:29 +08:00
|
|
|
def wrapper(*args, **kwargs):
|
2025-05-16 14:00:01 +02:00
|
|
|
_openssl_availabillity(digestname, usedforsecurity=usedforsecurity)
|
|
|
|
|
return func(*args, **kwargs)
|
|
|
|
|
return wrapper
|
|
|
|
|
|
|
|
|
|
def decorator(func_or_class):
|
|
|
|
|
return _decorate_func_or_class(func_or_class, decorator_func)
|
|
|
|
|
return decorator
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def find_openssl_hashdigest_constructor(digestname, *, usedforsecurity=True):
|
|
|
|
|
"""Find the OpenSSL hash function constructor by its name."""
|
|
|
|
|
assert isinstance(digestname, str), digestname
|
|
|
|
|
_openssl_availabillity(digestname, usedforsecurity=usedforsecurity)
|
|
|
|
|
# This returns a function of the form _hashlib.openssl_<name> and
|
|
|
|
|
# not a lambda function as it is rejected by _hashlib.hmac_new().
|
|
|
|
|
return getattr(_hashlib, f"openssl_{digestname}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def requires_builtin_hashdigest(
|
|
|
|
|
module_name, digestname, *, usedforsecurity=True
|
|
|
|
|
):
|
|
|
|
|
"""Decorator raising SkipTest if a HACL* hashing algorithm is missing.
|
|
|
|
|
|
|
|
|
|
- The *module_name* is the C extension module name based on HACL*.
|
|
|
|
|
- The *digestname* is one of its member, e.g., 'md5'.
|
|
|
|
|
"""
|
2025-07-13 10:58:15 +02:00
|
|
|
assert isinstance(digestname, str), digestname
|
2025-05-16 14:00:01 +02:00
|
|
|
def decorator_func(func):
|
|
|
|
|
@functools.wraps(func)
|
|
|
|
|
def wrapper(*args, **kwargs):
|
|
|
|
|
module = import_module(module_name)
|
2020-04-29 09:11:29 +08:00
|
|
|
try:
|
2025-05-16 14:00:01 +02:00
|
|
|
getattr(module, digestname)
|
|
|
|
|
except AttributeError:
|
|
|
|
|
fullname = f'{module_name}.{digestname}'
|
|
|
|
|
_missing_hash(fullname, implementation="HACL")
|
2025-03-17 11:10:03 +01:00
|
|
|
return func(*args, **kwargs)
|
2020-04-29 09:11:29 +08:00
|
|
|
return wrapper
|
2025-03-17 11:10:03 +01:00
|
|
|
|
|
|
|
|
def decorator(func_or_class):
|
|
|
|
|
return _decorate_func_or_class(func_or_class, decorator_func)
|
2020-04-29 09:11:29 +08:00
|
|
|
return decorator
|
2025-04-28 00:20:15 +02:00
|
|
|
|
|
|
|
|
|
2025-05-16 14:00:01 +02:00
|
|
|
def find_builtin_hashdigest_constructor(
|
|
|
|
|
module_name, digestname, *, usedforsecurity=True
|
|
|
|
|
):
|
|
|
|
|
"""Find the HACL* hash function constructor.
|
|
|
|
|
|
|
|
|
|
- The *module_name* is the C extension module name based on HACL*.
|
|
|
|
|
- The *digestname* is one of its member, e.g., 'md5'.
|
|
|
|
|
"""
|
2025-07-13 10:58:15 +02:00
|
|
|
assert isinstance(digestname, str), digestname
|
2025-05-16 14:00:01 +02:00
|
|
|
module = import_module(module_name)
|
|
|
|
|
try:
|
|
|
|
|
constructor = getattr(module, digestname)
|
|
|
|
|
constructor(b'', usedforsecurity=usedforsecurity)
|
|
|
|
|
except (AttributeError, TypeError, ValueError):
|
|
|
|
|
_missing_hash(f'{module_name}.{digestname}', implementation="HACL")
|
|
|
|
|
return constructor
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class HashFunctionsTrait:
|
|
|
|
|
"""Mixin trait class containing hash functions.
|
|
|
|
|
|
|
|
|
|
This class is assumed to have all unitest.TestCase methods but should
|
|
|
|
|
not directly inherit from it to prevent the test suite being run on it.
|
|
|
|
|
|
|
|
|
|
Subclasses should implement the hash functions by returning an object
|
|
|
|
|
that can be recognized as a valid digestmod parameter for both hashlib
|
|
|
|
|
and HMAC. In particular, it cannot be a lambda function as it will not
|
|
|
|
|
be recognized by hashlib (it will still be accepted by the pure Python
|
|
|
|
|
implementation of HMAC).
|
|
|
|
|
"""
|
|
|
|
|
|
2025-07-13 10:58:15 +02:00
|
|
|
DIGEST_NAMES = [
|
2025-05-16 14:00:01 +02:00
|
|
|
'md5', 'sha1',
|
|
|
|
|
'sha224', 'sha256', 'sha384', 'sha512',
|
|
|
|
|
'sha3_224', 'sha3_256', 'sha3_384', 'sha3_512',
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
# Default 'usedforsecurity' to use when looking up a hash function.
|
|
|
|
|
usedforsecurity = True
|
|
|
|
|
|
2025-07-13 10:58:15 +02:00
|
|
|
@classmethod
|
|
|
|
|
def setUpClass(cls):
|
|
|
|
|
super().setUpClass()
|
|
|
|
|
assert CANONICAL_DIGEST_NAMES.issuperset(cls.DIGEST_NAMES)
|
|
|
|
|
|
|
|
|
|
def is_valid_digest_name(self, digestname):
|
|
|
|
|
self.assertIn(digestname, self.DIGEST_NAMES)
|
|
|
|
|
|
|
|
|
|
def _find_constructor(self, digestname):
|
2025-05-16 14:00:01 +02:00
|
|
|
# By default, a missing algorithm skips the test that uses it.
|
2025-07-13 10:58:15 +02:00
|
|
|
self.is_valid_digest_name(digestname)
|
|
|
|
|
self.skipTest(f"missing hash function: {digestname}")
|
2025-05-16 14:00:01 +02:00
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def md5(self):
|
|
|
|
|
return self._find_constructor("md5")
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def sha1(self):
|
|
|
|
|
return self._find_constructor("sha1")
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def sha224(self):
|
|
|
|
|
return self._find_constructor("sha224")
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def sha256(self):
|
|
|
|
|
return self._find_constructor("sha256")
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def sha384(self):
|
|
|
|
|
return self._find_constructor("sha384")
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def sha512(self):
|
|
|
|
|
return self._find_constructor("sha512")
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def sha3_224(self):
|
|
|
|
|
return self._find_constructor("sha3_224")
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def sha3_256(self):
|
|
|
|
|
return self._find_constructor("sha3_256")
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def sha3_384(self):
|
|
|
|
|
return self._find_constructor("sha3_384")
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def sha3_512(self):
|
|
|
|
|
return self._find_constructor("sha3_512")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class NamedHashFunctionsTrait(HashFunctionsTrait):
|
|
|
|
|
"""Trait containing named hash functions.
|
|
|
|
|
|
|
|
|
|
Hash functions are available if and only if they are available in hashlib.
|
|
|
|
|
"""
|
|
|
|
|
|
2025-07-13 10:58:15 +02:00
|
|
|
def _find_constructor(self, digestname):
|
|
|
|
|
self.is_valid_digest_name(digestname)
|
|
|
|
|
return digestname
|
2025-05-16 14:00:01 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class OpenSSLHashFunctionsTrait(HashFunctionsTrait):
|
|
|
|
|
"""Trait containing OpenSSL hash functions.
|
|
|
|
|
|
|
|
|
|
Hash functions are available if and only if they are available in _hashlib.
|
|
|
|
|
"""
|
|
|
|
|
|
2025-07-13 10:58:15 +02:00
|
|
|
def _find_constructor(self, digestname):
|
|
|
|
|
self.is_valid_digest_name(digestname)
|
2025-05-16 14:00:01 +02:00
|
|
|
return find_openssl_hashdigest_constructor(
|
2025-07-13 10:58:15 +02:00
|
|
|
digestname, usedforsecurity=self.usedforsecurity
|
2025-05-16 14:00:01 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class BuiltinHashFunctionsTrait(HashFunctionsTrait):
|
|
|
|
|
"""Trait containing HACL* hash functions.
|
|
|
|
|
|
|
|
|
|
Hash functions are available if and only if they are available in C.
|
|
|
|
|
In particular, HACL* HMAC-MD5 may be available even though HACL* md5
|
|
|
|
|
is not since the former is unconditionally built.
|
|
|
|
|
"""
|
|
|
|
|
|
2025-07-13 10:58:15 +02:00
|
|
|
def _find_constructor_in(self, module, digestname):
|
|
|
|
|
self.is_valid_digest_name(digestname)
|
|
|
|
|
return find_builtin_hashdigest_constructor(module, digestname)
|
2025-05-16 14:00:01 +02:00
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def md5(self):
|
|
|
|
|
return self._find_constructor_in("_md5", "md5")
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def sha1(self):
|
|
|
|
|
return self._find_constructor_in("_sha1", "sha1")
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def sha224(self):
|
|
|
|
|
return self._find_constructor_in("_sha2", "sha224")
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def sha256(self):
|
|
|
|
|
return self._find_constructor_in("_sha2", "sha256")
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def sha384(self):
|
|
|
|
|
return self._find_constructor_in("_sha2", "sha384")
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def sha512(self):
|
|
|
|
|
return self._find_constructor_in("_sha2", "sha512")
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def sha3_224(self):
|
|
|
|
|
return self._find_constructor_in("_sha3", "sha3_224")
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def sha3_256(self):
|
|
|
|
|
return self._find_constructor_in("_sha3","sha3_256")
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def sha3_384(self):
|
|
|
|
|
return self._find_constructor_in("_sha3","sha3_384")
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def sha3_512(self):
|
|
|
|
|
return self._find_constructor_in("_sha3","sha3_512")
|
|
|
|
|
|
|
|
|
|
|
2025-04-28 00:20:15 +02:00
|
|
|
def find_gil_minsize(modules_names, default=2048):
|
|
|
|
|
"""Get the largest GIL_MINSIZE value for the given cryptographic modules.
|
|
|
|
|
|
|
|
|
|
The valid module names are the following:
|
|
|
|
|
|
|
|
|
|
- _hashlib
|
|
|
|
|
- _md5, _sha1, _sha2, _sha3, _blake2
|
|
|
|
|
- _hmac
|
|
|
|
|
"""
|
|
|
|
|
sizes = []
|
|
|
|
|
for module_name in modules_names:
|
|
|
|
|
try:
|
|
|
|
|
module = importlib.import_module(module_name)
|
|
|
|
|
except ImportError:
|
|
|
|
|
continue
|
|
|
|
|
sizes.append(getattr(module, '_GIL_MINSIZE', default))
|
|
|
|
|
return max(sizes, default=default)
|
2025-07-13 10:58:15 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def _block_openssl_hash_new(blocked_name):
|
|
|
|
|
"""Block OpenSSL implementation of _hashlib.new()."""
|
|
|
|
|
assert isinstance(blocked_name, str), blocked_name
|
|
|
|
|
if _hashlib is None:
|
|
|
|
|
return contextlib.nullcontext()
|
|
|
|
|
@functools.wraps(wrapped := _hashlib.new)
|
|
|
|
|
def wrapper(name, data=b'', *, usedforsecurity=True, string=None):
|
|
|
|
|
if name == blocked_name:
|
|
|
|
|
raise _hashlib.UnsupportedDigestmodError(blocked_name)
|
|
|
|
|
return wrapped(*args, **kwargs)
|
|
|
|
|
_ensure_wrapper_signature(wrapper, wrapped)
|
|
|
|
|
return unittest.mock.patch('_hashlib.new', wrapper)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _block_openssl_hmac_new(blocked_name):
|
|
|
|
|
"""Block OpenSSL HMAC-HASH implementation."""
|
|
|
|
|
assert isinstance(blocked_name, str), blocked_name
|
|
|
|
|
if _hashlib is None:
|
|
|
|
|
return contextlib.nullcontext()
|
|
|
|
|
@functools.wraps(wrapped := _hashlib.hmac_new)
|
|
|
|
|
def wrapper(key, msg=b'', digestmod=None):
|
|
|
|
|
if digestmod == blocked_name:
|
|
|
|
|
raise _hashlib.UnsupportedDigestmodError(blocked_name)
|
|
|
|
|
return wrapped(key, msg, digestmod)
|
|
|
|
|
_ensure_wrapper_signature(wrapper, wrapped)
|
|
|
|
|
return unittest.mock.patch('_hashlib.hmac_new', wrapper)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _block_openssl_hmac_digest(blocked_name):
|
|
|
|
|
"""Block OpenSSL HMAC-HASH one-shot digest implementation."""
|
|
|
|
|
assert isinstance(blocked_name, str), blocked_name
|
|
|
|
|
if _hashlib is None:
|
|
|
|
|
return contextlib.nullcontext()
|
|
|
|
|
@functools.wraps(wrapped := _hashlib.hmac_digest)
|
|
|
|
|
def wrapper(key, msg, digest):
|
|
|
|
|
if digest == blocked_name:
|
|
|
|
|
raise _hashlib.UnsupportedDigestmodError(blocked_name)
|
|
|
|
|
return wrapped(key, msg, digestmod)
|
|
|
|
|
_ensure_wrapper_signature(wrapper, wrapped)
|
|
|
|
|
return unittest.mock.patch('_hashlib.hmac_digest', wrapper)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@contextlib.contextmanager
|
|
|
|
|
def _block_builtin_hash_new(name):
|
|
|
|
|
assert isinstance(name, str), name
|
|
|
|
|
assert name.lower() == name, f"invalid name: {name}"
|
|
|
|
|
|
|
|
|
|
builtin_cache = getattr(hashlib, '__builtin_constructor_cache')
|
|
|
|
|
if name in builtin_cache:
|
|
|
|
|
f = builtin_cache.pop(name)
|
|
|
|
|
F = builtin_cache.pop(name.upper(), None)
|
|
|
|
|
else:
|
|
|
|
|
f = F = None
|
|
|
|
|
try:
|
|
|
|
|
yield
|
|
|
|
|
finally:
|
|
|
|
|
if f is not None:
|
|
|
|
|
builtin_cache[name] = f
|
|
|
|
|
if F is not None:
|
|
|
|
|
builtin_cache[name.upper()] = F
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _block_builtin_hmac_new(blocked_name):
|
|
|
|
|
assert isinstance(blocked_name, str), blocked_name
|
|
|
|
|
if _hmac is None:
|
|
|
|
|
return contextlib.nullcontext()
|
|
|
|
|
@functools.wraps(wrapped := _hmac.new)
|
|
|
|
|
def wrapper(key, msg=None, digestmod=None):
|
|
|
|
|
if digestmod == blocked_name:
|
|
|
|
|
raise _hmac.UnknownHashError(blocked_name)
|
|
|
|
|
return wrapped(key, msg, digestmod)
|
|
|
|
|
_ensure_wrapper_signature(wrapper, wrapped)
|
|
|
|
|
return unittest.mock.patch('_hmac.new', wrapper)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _block_builtin_hmac_digest(blocked_name):
|
|
|
|
|
assert isinstance(blocked_name, str), blocked_name
|
|
|
|
|
if _hmac is None:
|
|
|
|
|
return contextlib.nullcontext()
|
|
|
|
|
@functools.wraps(wrapped := _hmac.compute_digest)
|
|
|
|
|
def wrapper(key, msg, digest):
|
|
|
|
|
if digest == blocked_name:
|
|
|
|
|
raise _hmac.UnknownHashError(blocked_name)
|
|
|
|
|
return wrapped(key, msg, digest)
|
|
|
|
|
_ensure_wrapper_signature(wrapper, wrapped)
|
|
|
|
|
return unittest.mock.patch('_hmac.compute_digest', wrapper)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _make_hash_constructor_blocker(name, dummy, *, interface):
|
|
|
|
|
assert isinstance(name, str), name
|
|
|
|
|
assert interface in ('builtin', 'openssl', 'hashlib')
|
|
|
|
|
assert name in _EXPLICIT_CONSTRUCTORS, f"invalid hash: {name}"
|
|
|
|
|
fullname = _EXPLICIT_CONSTRUCTORS[name].fullname(interface)
|
|
|
|
|
if fullname is None:
|
|
|
|
|
# function shouldn't exist for this implementation
|
|
|
|
|
return contextlib.nullcontext()
|
|
|
|
|
assert fullname.count('.') == 1, fullname
|
|
|
|
|
module_name, method = fullname.split('.', maxsplit=1)
|
|
|
|
|
try:
|
|
|
|
|
module = importlib.import_module(module_name)
|
|
|
|
|
except ImportError:
|
|
|
|
|
# module is already disabled
|
|
|
|
|
return contextlib.nullcontext()
|
|
|
|
|
wrapped = getattr(module, method)
|
|
|
|
|
wrapper = functools.wraps(wrapped)(dummy)
|
|
|
|
|
_ensure_wrapper_signature(wrapper, wrapped)
|
|
|
|
|
return unittest.mock.patch(fullname, wrapper)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _block_hashlib_hash_constructor(name):
|
|
|
|
|
"""Block explicit public constructors."""
|
|
|
|
|
assert isinstance(name, str), name
|
|
|
|
|
def dummy(data=b'', *, usedforsecurity=True, string=None):
|
|
|
|
|
raise ValueError(f"unsupported hash name: {name}")
|
|
|
|
|
return _make_hash_constructor_blocker(name, dummy, interface='hashlib')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _block_openssl_hash_constructor(name):
|
|
|
|
|
"""Block explicit OpenSSL constructors."""
|
|
|
|
|
assert isinstance(name, str), name
|
|
|
|
|
def dummy(data=b'', *, usedforsecurity=True, string=None):
|
|
|
|
|
raise ValueError(f"unsupported hash name: {name}")
|
|
|
|
|
return _make_hash_constructor_blocker(name, dummy, interface='openssl')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _block_builtin_hash_constructor(name):
|
|
|
|
|
"""Block explicit HACL* constructors."""
|
|
|
|
|
assert isinstance(name, str), name
|
|
|
|
|
def dummy(data=b'', *, usedforsecurity=True, string=b''):
|
|
|
|
|
raise ValueError(f"unsupported hash name: {name}")
|
|
|
|
|
return _make_hash_constructor_blocker(name, dummy, interface='builtin')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _block_builtin_hmac_constructor(name):
|
|
|
|
|
"""Block explicit HACL* HMAC constructors."""
|
|
|
|
|
assert isinstance(name, str), name
|
|
|
|
|
assert name in _EXPLICIT_HMAC_CONSTRUCTORS, f"invalid hash: {name}"
|
|
|
|
|
fullname = _EXPLICIT_HMAC_CONSTRUCTORS[name]
|
|
|
|
|
if fullname is None:
|
|
|
|
|
# function shouldn't exist for this implementation
|
|
|
|
|
return contextlib.nullcontext()
|
|
|
|
|
assert fullname.count('.') == 1, fullname
|
|
|
|
|
module_name, method = fullname.split('.', maxsplit=1)
|
|
|
|
|
assert module_name == '_hmac', module_name
|
|
|
|
|
try:
|
|
|
|
|
module = importlib.import_module(module_name)
|
|
|
|
|
except ImportError:
|
|
|
|
|
# module is already disabled
|
|
|
|
|
return contextlib.nullcontext()
|
|
|
|
|
@functools.wraps(wrapped := getattr(module, method))
|
|
|
|
|
def wrapper(key, obj):
|
|
|
|
|
raise ValueError(f"unsupported hash name: {name}")
|
|
|
|
|
_ensure_wrapper_signature(wrapper, wrapped)
|
|
|
|
|
return unittest.mock.patch(fullname, wrapper)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@contextlib.contextmanager
|
|
|
|
|
def block_algorithm(name, *, allow_openssl=False, allow_builtin=False):
|
|
|
|
|
"""Block a hash algorithm for both hashing and HMAC.
|
|
|
|
|
|
|
|
|
|
Be careful with this helper as a function may be allowed, but can
|
|
|
|
|
still raise a ValueError at runtime if the OpenSSL security policy
|
|
|
|
|
disables it, e.g., if allow_openssl=True and FIPS mode is on.
|
|
|
|
|
"""
|
|
|
|
|
with contextlib.ExitStack() as stack:
|
|
|
|
|
if not (allow_openssl or allow_builtin):
|
|
|
|
|
# If one of the private interface is allowed, then the
|
|
|
|
|
# public interface will fallback to it even though the
|
|
|
|
|
# comment in hashlib.py says otherwise.
|
|
|
|
|
#
|
|
|
|
|
# So we should only block it if the private interfaces
|
|
|
|
|
# are blocked as well.
|
|
|
|
|
stack.enter_context(_block_hashlib_hash_constructor(name))
|
|
|
|
|
if not allow_openssl:
|
|
|
|
|
stack.enter_context(_block_openssl_hash_new(name))
|
|
|
|
|
stack.enter_context(_block_openssl_hmac_new(name))
|
|
|
|
|
stack.enter_context(_block_openssl_hmac_digest(name))
|
|
|
|
|
stack.enter_context(_block_openssl_hash_constructor(name))
|
|
|
|
|
if not allow_builtin:
|
|
|
|
|
stack.enter_context(_block_builtin_hash_new(name))
|
|
|
|
|
stack.enter_context(_block_builtin_hmac_new(name))
|
|
|
|
|
stack.enter_context(_block_builtin_hmac_digest(name))
|
|
|
|
|
stack.enter_context(_block_builtin_hash_constructor(name))
|
|
|
|
|
stack.enter_context(_block_builtin_hmac_constructor(name))
|
|
|
|
|
yield
|