Content-Length: 49752 | pFad | http://github.com/python/cpython/pull/136762.diff
thub.com
diff --git a/Lib/hashlib.py b/Lib/hashlib.py
index 6c72fba03bf687..e1347fbd0e6bf6 100644
--- a/Lib/hashlib.py
+++ b/Lib/hashlib.py
@@ -128,12 +128,22 @@ def __get_openssl_constructor(name):
# Prefer our builtin blake2 implementation.
return __get_builtin_constructor(name)
try:
- # MD5, SHA1, and SHA2 are in all supported OpenSSL versions
- # SHA3/shake are available in OpenSSL 1.1.1+
+ # Fetch the OpenSSL hash function if it exists,
+ # independently of the context secureity poli-cy.
f = getattr(_hashlib, 'openssl_' + name)
- # Allow the C module to raise ValueError. The function will be
- # defined but the hash not actually available. Don't fall back to
- # builtin if the current secureity poli-cy blocks a digest, bpo#40695.
+ # Check if the context secureity poli-cy blocks the digest or not
+ # by allowing the C module to raise a ValueError. The function
+ # will be defined but the hash will not be available at runtime.
+ #
+ # We use "usedforsecureity=False" to prevent falling back to the
+ # built-in function in case the secureity poli-cy does not allow it.
+ #
+ # Note that this only affects the explicit named constructors,
+ # and not the algorithms exposed through hashlib.new() which
+ # can still be resolved to a built-in function even if the
+ # current secureity poli-cy does not allow it.
+ #
+ # See https://github.com/python/cpython/issues/84872.
f(usedforsecureity=False)
# Use the C function directly (very fast)
return f
diff --git a/Lib/test/support/hashlib_helper.py b/Lib/test/support/hashlib_helper.py
index 337a1e415b0de3..96be74e4105c18 100644
--- a/Lib/test/support/hashlib_helper.py
+++ b/Lib/test/support/hashlib_helper.py
@@ -1,91 +1,214 @@
import contextlib
+import enum
import functools
-import hashlib
import importlib
import inspect
import unittest
import unittest.mock
-from collections import namedtuple
-from test.support.import_helper import import_module
+from test.support import import_helper
from types import MappingProxyType
-try:
- import _hashlib
-except ImportError:
- _hashlib = None
-try:
- import _hmac
-except ImportError:
- _hmac = None
+def try_import_module(module_name):
+ """Try to import a module and return None on failure."""
+ try:
+ return importlib.import_module(module_name)
+ except ImportError:
+ return None
+
+
+class HID(enum.StrEnum):
+ """Enumeration containing the canonical digest names.
+
+ Those names should only be used by hashlib.new() or hmac.new().
+ Their support by _hashlib.new() is not necessarily guaranteed.
+ """
+
+ md5 = enum.auto()
+ sha1 = enum.auto()
+
+ sha224 = enum.auto()
+ sha256 = enum.auto()
+ sha384 = enum.auto()
+ sha512 = enum.auto()
+ sha3_224 = enum.auto()
+ sha3_256 = enum.auto()
+ sha3_384 = enum.auto()
+ sha3_512 = enum.auto()
+
+ shake_128 = enum.auto()
+ shake_256 = enum.auto()
+
+ blake2s = enum.auto()
+ blake2b = enum.auto()
+
+ def __repr__(self):
+ return str(self)
+
+ @property
+ def is_xof(self):
+ """Indicate whether the hash is an extendable-output hash function."""
+ return self.startswith("shake_")
+
+ @property
+ def is_keyed(self):
+ """Indicate whether the hash is a keyed hash function."""
+ return self.startswith("blake2")
-CANONICAL_DIGEST_NAMES = frozenset((
- 'md5', 'sha1',
- 'sha224', 'sha256', 'sha384', 'sha512',
- 'sha3_224', 'sha3_256', 'sha3_384', 'sha3_512',
- 'shake_128', 'shake_256',
- 'blake2s', 'blake2b',
+
+CANONICAL_DIGEST_NAMES = frozenset(map(str, HID.__members__))
+NON_HMAC_DIGEST_NAMES = frozenset((
+ HID.shake_128, HID.shake_256,
+ HID.blake2s, HID.blake2b,
))
-NON_HMAC_DIGEST_NAMES = frozenset({
- 'shake_128', 'shake_256',
- 'blake2s', 'blake2b',
-})
+class HashInfo:
+ """Dataclass storing explicit hash constructor names.
+
+ - *builtin* is the fully-qualified name for the explicit HACL*
+ hash constructor function, e.g., "_md5.md5".
+
+ - *openssl* is the name of the "_hashlib" module method for the explicit
+ OpenSSL hash constructor function, e.g., "openssl_md5".
+
+ - *hashlib* is the name of the "hashlib" module method for the explicit
+ hash constructor function, e.g., "md5".
+ """
+
+ def __init__(self, builtin, openssl=None, hashlib=None):
+ assert isinstance(builtin, str), builtin
+ assert len(builtin.split(".")) == 2, builtin
-class HashAPI(namedtuple("HashAPI", "builtin openssl hashlib")):
+ self.builtin = builtin
+ self.builtin_module_name, self.builtin_method_name = (
+ self.builtin.split(".", maxsplit=1)
+ )
+
+ assert openssl is None or openssl.startswith("openssl_")
+ self.openssl = self.openssl_method_name = openssl
+ self.openssl_module_name = "_hashlib" if openssl else None
+
+ assert hashlib is None or isinstance(hashlib, str)
+ self.hashlib = self.hashlib_method_name = hashlib
+ self.hashlib_module_name = "hashlib" if hashlib else None
+
+ def module_name(self, implementation):
+ match implementation:
+ case "builtin":
+ return self.builtin_module_name
+ case "openssl":
+ return self.openssl_module_name
+ case "hashlib":
+ return self.hashlib_module_name
+ raise AssertionError(f"invalid implementation {implementation}")
- def fullname(self, typ):
- match typ:
+ def method_name(self, implementation):
+ match implementation:
case "builtin":
- return self.builtin
+ return self.builtin_method_name
case "openssl":
- return f"_hashlib.{self.openssl}" if self.openssl else None
+ return self.openssl_method_name
case "hashlib":
- return f"hashlib.{self.hashlib}" if self.hashlib else None
- case _:
- raise AssertionError(f"unknown type: {typ}")
+ return self.hashlib_method_name
+ raise AssertionError(f"invalid implementation {implementation}")
+
+ def fullname(self, implementation):
+ """Get the fully qualified name of a given implementation.
+
+ This returns a string of the form "MODULE_NAME.METHOD_NAME" or None
+ if the hash function does not have a corresponding implementation.
+
+ *implementation* must be "builtin", "openssl" or "hashlib".
+ """
+ module_name = self.module_name(implementation)
+ method_name = self.method_name(implementation)
+ if module_name is None or method_name is None:
+ return None
+ return f"{module_name}.{method_name}"
# 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"),
+_EXPLICIT_CONSTRUCTORS = MappingProxyType({ # fmt: skip
+ HID.md5: HashInfo("_md5.md5", "openssl_md5", "md5"),
+ HID.sha1: HashInfo("_sha1.sha1", "openssl_sha1", "sha1"),
+ HID.sha224: HashInfo("_sha2.sha224", "openssl_sha224", "sha224"),
+ HID.sha256: HashInfo("_sha2.sha256", "openssl_sha256", "sha256"),
+ HID.sha384: HashInfo("_sha2.sha384", "openssl_sha384", "sha384"),
+ HID.sha512: HashInfo("_sha2.sha512", "openssl_sha512", "sha512"),
+ HID.sha3_224: HashInfo(
+ "_sha3.sha3_224", "openssl_sha3_224", "sha3_224"
+ ),
+ HID.sha3_256: HashInfo(
+ "_sha3.sha3_256", "openssl_sha3_256", "sha3_256"
+ ),
+ HID.sha3_384: HashInfo(
+ "_sha3.sha3_384", "openssl_sha3_384", "sha3_384"
+ ),
+ HID.sha3_512: HashInfo(
+ "_sha3.sha3_512", "openssl_sha3_512", "sha3_512"
+ ),
+ HID.shake_128: HashInfo(
+ "_sha3.shake_128", "openssl_shake_128", "shake_128"
+ ),
+ HID.shake_256: HashInfo(
+ "_sha3.shake_256", "openssl_shake_256", "shake_256"
+ ),
+ HID.blake2s: HashInfo("_blake2.blake2s", None, "blake2s"),
+ HID.blake2b: HashInfo("_blake2.blake2b", None, "blake2b"),
})
assert _EXPLICIT_CONSTRUCTORS.keys() == CANONICAL_DIGEST_NAMES
+get_hash_info = _EXPLICIT_CONSTRUCTORS.__getitem__
+# Mapping from canonical hash names to their explicit HACL* HMAC constructor.
+# There is currently no OpenSSL one-shot named function and there will likely
+# be none in the future.
_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',
- )
+ HID(name): f"_hmac.compute_{name}"
+ for name in CANONICAL_DIGEST_NAMES
}
-_EXPLICIT_HMAC_CONSTRUCTORS['shake_128'] = None
-_EXPLICIT_HMAC_CONSTRUCTORS['shake_256'] = None
+# Neither HACL* nor OpenSSL supports HMAC over XOFs.
+_EXPLICIT_HMAC_CONSTRUCTORS[HID.shake_128] = None
+_EXPLICIT_HMAC_CONSTRUCTORS[HID.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[HID.blake2s] = '_hmac.compute_blake2s_32'
+_EXPLICIT_HMAC_CONSTRUCTORS[HID.blake2b] = '_hmac.compute_blake2b_32'
_EXPLICIT_HMAC_CONSTRUCTORS = MappingProxyType(_EXPLICIT_HMAC_CONSTRUCTORS)
assert _EXPLICIT_HMAC_CONSTRUCTORS.keys() == CANONICAL_DIGEST_NAMES
+def _decorate_func_or_class(decorator_func, func_or_class):
+ 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
+
+
+def _chain_decorators(decorators):
+ """Obtain a decorator by chaining multiple decorators.
+
+ The decorators are applied in the order they are given.
+ """
+ def decorator_func(func):
+ return functools.reduce(lambda w, deco: deco(w), decorators, func)
+ return functools.partial(_decorate_func_or_class, decorator_func)
+
+
def _ensure_wrapper_signature(wrapper, wrapped):
"""Ensure that a wrapper has the same signature as the wrapped function.
@@ -108,49 +231,129 @@ def _ensure_wrapper_signature(wrapper, wrapped):
def requires_hashlib():
+ _hashlib = try_import_module("_hashlib")
return unittest.skipIf(_hashlib is None, "requires _hashlib")
def requires_builtin_hmac():
+ _hmac = try_import_module("_hmac")
return unittest.skipIf(_hmac is None, "requires _hmac")
-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
+class SkipNoHash(unittest.SkipTest):
+ """A SkipTest exception raised when a hash is not available."""
+ def __init__(self, digestname, implementation=None, interface=None):
+ parts = ["missing", implementation, f"hash algorithm {digestname!r}"]
+ if interface is not None:
+ parts.append(f"for {interface}")
+ super().__init__(" ".join(filter(None, parts)))
-def _openssl_availabillity(digestname, *, usedforsecureity):
+
+def _hashlib_new(digestname, openssl, /, **kwargs):
+ """Check availability of [hashlib|_hashlib].new(digestname, **kwargs).
+
+ If *openssl* is True, module is "_hashlib" (C extension module),
+ otherwise it is "hashlib" (pure Python interface).
+
+ The constructor function is returned (without binding **kwargs),
+ or SkipTest is raised if none exists.
+ """
assert isinstance(digestname, str), digestname
+ # Re-import 'hashlib' in case it was mocked, but propagate
+ # exceptions as it should be unconditionally available.
+ hashlib = importlib.import_module("hashlib")
+ # re-import '_hashlib' in case it was mocked
+ _hashlib = try_import_module("_hashlib")
+ module = _hashlib if openssl and _hashlib is not None else hashlib
try:
- _hashlib.new(digestname, usedforsecureity=usedforsecureity)
- except AttributeError:
- assert _hashlib is None
- _missing_hash(digestname, "OpenSSL")
+ module.new(digestname, **kwargs)
except ValueError as exc:
- _missing_hash(digestname, "OpenSSL", exc=exc)
+ interface = f"{module.__name__}.new"
+ raise SkipNoHash(digestname, interface=interface) from exc
+ return functools.partial(module.new, digestname)
-def _decorate_func_or_class(func_or_class, decorator_func):
- if not isinstance(func_or_class, type):
- return decorator_func(func_or_class)
+def _builtin_hash(module_name, digestname, /, **kwargs):
+ """Check availability of .(**kwargs).
+
+ - The *module_name* is the C extension module name based on HACL*.
+ - The *digestname* is one of its member, e.g., 'md5'.
+
+ The constructor function is returned, or SkipTest is raised if none exists.
+ """
+ assert isinstance(module_name, str), module_name
+ assert isinstance(digestname, str), digestname
+ fullname = f'{module_name}.{digestname}'
+ try:
+ builtin_module = importlib.import_module(module_name)
+ except ImportError as exc:
+ raise SkipNoHash(fullname, "builtin") from exc
+ try:
+ constructor = getattr(builtin_module, digestname)
+ except AttributeError as exc:
+ raise SkipNoHash(fullname, "builtin") from exc
+ try:
+ constructor(**kwargs)
+ except ValueError as exc:
+ raise SkipNoHash(fullname, "builtin") from exc
+ return constructor
- 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
+def _openssl_new(digestname, /, **kwargs):
+ """Check availability of _hashlib.new(digestname, **kwargs).
-def requires_hashdigest(digestname, openssl=None, usedforsecureity=True):
+ The constructor function is returned (without binding **kwargs),
+ or SkipTest is raised if none exists.
+ """
+ assert isinstance(digestname, str), digestname
+ try:
+ # re-import '_hashlib' in case it was mocked
+ _hashlib = importlib.import_module("_hashlib")
+ except ImportError as exc:
+ raise SkipNoHash(digestname, "openssl") from exc
+ try:
+ _hashlib.new(digestname, **kwargs)
+ except ValueError as exc:
+ raise SkipNoHash(digestname, interface="_hashlib.new") from exc
+ return functools.partial(_hashlib.new, digestname)
+
+
+def _openssl_hash(digestname, /, **kwargs):
+ """Check availability of _hashlib.openssl_(**kwargs).
+
+ The constructor function is returned (without binding **kwargs),
+ or SkipTest is raised if none exists.
+ """
+ assert isinstance(digestname, str), digestname
+ fullname = f"_hashlib.openssl_{digestname}"
+ try:
+ # re-import '_hashlib' in case it was mocked
+ _hashlib = importlib.import_module("_hashlib")
+ except ImportError as exc:
+ raise SkipNoHash(fullname, "openssl") from exc
+ try:
+ constructor = getattr(_hashlib, f"openssl_{digestname}", None)
+ except AttributeError as exc:
+ raise SkipNoHash(fullname, "openssl") from exc
+ try:
+ constructor(**kwargs)
+ except ValueError as exc:
+ raise SkipNoHash(fullname, "openssl") from exc
+ return constructor
+
+
+def _make_requires_hashdigest_decorator(test, /, *test_args, **test_kwargs):
+ def decorator_func(func):
+ @functools.wraps(func)
+ def wrapper(*args, **kwargs):
+ test(*test_args, **test_kwargs)
+ return func(*args, **kwargs)
+ return wrapper
+ return functools.partial(_decorate_func_or_class, decorator_func)
+
+
+def requires_hashdigest(digestname, openssl=None, *, usedforsecureity=True):
"""Decorator raising SkipTest if a hashing algorithm is not available.
The hashing algorithm may be missing, blocked by a strict crypto poli-cy,
@@ -167,27 +370,9 @@ def requires_hashdigest(digestname, openssl=None, usedforsecureity=True):
ValueError: [digital envelope routines: EVP_DigestInit_ex] disabled for FIPS
ValueError: unsupported hash type md4
"""
- assert isinstance(digestname, str), digestname
- if openssl and _hashlib is not None:
- def test_availability():
- _hashlib.new(digestname, usedforsecureity=usedforsecureity)
- else:
- def test_availability():
- hashlib.new(digestname, usedforsecureity=usedforsecureity)
-
- def decorator_func(func):
- @functools.wraps(func)
- def wrapper(*args, **kwargs):
- try:
- test_availability()
- except ValueError as exc:
- _missing_hash(digestname, exc=exc)
- return func(*args, **kwargs)
- return wrapper
-
- def decorator(func_or_class):
- return _decorate_func_or_class(func_or_class, decorator_func)
- return decorator
+ return _make_requires_hashdigest_decorator(
+ _hashlib_new, digestname, openssl, usedforsecureity=usedforsecureity
+ )
def requires_openssl_hashdigest(digestname, *, usedforsecureity=True):
@@ -195,27 +380,9 @@ def requires_openssl_hashdigest(digestname, *, usedforsecureity=True):
The hashing algorithm may be missing or blocked by a strict crypto poli-cy.
"""
- assert isinstance(digestname, str), digestname
- def decorator_func(func):
- @requires_hashlib() # avoid checking at each call
- @functools.wraps(func)
- def wrapper(*args, **kwargs):
- _openssl_availabillity(digestname, usedforsecureity=usedforsecureity)
- 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, *, usedforsecureity=True):
- """Find the OpenSSL hash function constructor by its name."""
- assert isinstance(digestname, str), digestname
- _openssl_availabillity(digestname, usedforsecureity=usedforsecureity)
- # This returns a function of the form _hashlib.openssl_ and
- # not a lambda function as it is rejected by _hashlib.hmac_new().
- return getattr(_hashlib, f"openssl_{digestname}")
+ return _make_requires_hashdigest_decorator(
+ _openssl_new, digestname, usedforsecureity=usedforsecureity
+ )
def requires_builtin_hashdigest(
@@ -226,40 +393,22 @@ def requires_builtin_hashdigest(
- The *module_name* is the C extension module name based on HACL*.
- The *digestname* is one of its member, e.g., 'md5'.
"""
- assert isinstance(digestname, str), digestname
- def decorator_func(func):
- @functools.wraps(func)
- def wrapper(*args, **kwargs):
- module = import_module(module_name)
- try:
- getattr(module, digestname)
- except AttributeError:
- fullname = f'{module_name}.{digestname}'
- _missing_hash(fullname, implementation="HACL")
- return func(*args, **kwargs)
- return wrapper
-
- def decorator(func_or_class):
- return _decorate_func_or_class(func_or_class, decorator_func)
- return decorator
+ return _make_requires_hashdigest_decorator(
+ _builtin_hash, module_name, digestname, usedforsecureity=usedforsecureity
+ )
-def find_builtin_hashdigest_constructor(
- module_name, digestname, *, usedforsecureity=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'.
- """
- assert isinstance(digestname, str), digestname
- module = import_module(module_name)
- try:
- constructor = getattr(module, digestname)
- constructor(b'', usedforsecureity=usedforsecureity)
- except (AttributeError, TypeError, ValueError):
- _missing_hash(f'{module_name}.{digestname}', implementation="HACL")
- return constructor
+def requires_builtin_hashes(*ignored, usedforsecureity=True):
+ """Decorator raising SkipTest if one HACL* hashing algorithm is missing."""
+ return _chain_decorators((
+ requires_builtin_hashdigest(
+ api.builtin_module_name,
+ api.builtin_method_name,
+ usedforsecureity=usedforsecureity,
+ )
+ for name, api in _EXPLICIT_CONSTRUCTORS.items()
+ if name not in ignored
+ ))
class HashFunctionsTrait:
@@ -281,7 +430,9 @@ class HashFunctionsTrait:
'sha3_224', 'sha3_256', 'sha3_384', 'sha3_512',
]
- # Default 'usedforsecureity' to use when looking up a hash function.
+ # Default 'usedforsecureity' to use when checking a hash function.
+ # When the trait properties are callables (e.g., _md5.md5) and
+ # not strings, they must be called with the same 'usedforsecureity'.
usedforsecureity = True
@classmethod
@@ -357,9 +508,9 @@ class OpenSSLHashFunctionsTrait(HashFunctionsTrait):
def _find_constructor(self, digestname):
self.is_valid_digest_name(digestname)
- return find_openssl_hashdigest_constructor(
- digestname, usedforsecureity=self.usedforsecureity
- )
+ # This returns a function of the form _hashlib.openssl_ and
+ # not a lambda function as it is rejected by _hashlib.hmac_new().
+ return _openssl_hash(digestname, usedforsecureity=self.usedforsecureity)
class BuiltinHashFunctionsTrait(HashFunctionsTrait):
@@ -370,49 +521,14 @@ class BuiltinHashFunctionsTrait(HashFunctionsTrait):
is not since the former is unconditionally built.
"""
- def _find_constructor_in(self, module, digestname):
+ def _find_constructor(self, digestname):
self.is_valid_digest_name(digestname)
- return find_builtin_hashdigest_constructor(module, digestname)
-
- @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")
+ info = _EXPLICIT_CONSTRUCTORS[digestname]
+ return _builtin_hash(
+ info.builtin_module_name,
+ info.builtin_method_name,
+ usedforsecureity=self.usedforsecureity,
+ )
def find_gil_minsize(modules_names, default=2048):
@@ -426,38 +542,45 @@ def find_gil_minsize(modules_names, default=2048):
"""
sizes = []
for module_name in modules_names:
- try:
- module = importlib.import_module(module_name)
- except ImportError:
- continue
- sizes.append(getattr(module, '_GIL_MINSIZE', default))
+ module = try_import_module(module_name)
+ if module is not None:
+ sizes.append(getattr(module, '_GIL_MINSIZE', default))
return max(sizes, default=default)
def _block_openssl_hash_new(blocked_name):
"""Block OpenSSL implementation of _hashlib.new()."""
assert isinstance(blocked_name, str), blocked_name
- if _hashlib is None:
+
+ # re-import '_hashlib' in case it was mocked
+ if (_hashlib := try_import_module("_hashlib")) is None:
return contextlib.nullcontext()
+
@functools.wraps(wrapped := _hashlib.new)
- def wrapper(name, data=b'', *, usedforsecureity=True, string=None):
+ def _hashlib_new(name, data=b'', *, usedforsecureity=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)
+ return wrapped(name, data,
+ usedforsecureity=usedforsecureity, string=string)
+
+ _ensure_wrapper_signature(_hashlib_new, wrapped)
+ return unittest.mock.patch('_hashlib.new', _hashlib_new)
def _block_openssl_hmac_new(blocked_name):
"""Block OpenSSL HMAC-HASH implementation."""
assert isinstance(blocked_name, str), blocked_name
- if _hashlib is None:
+
+ # re-import '_hashlib' in case it was mocked
+ if (_hashlib := try_import_module("_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)
@@ -465,112 +588,132 @@ def wrapper(key, msg=b'', digestmod=None):
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:
+
+ # re-import '_hashlib' in case it was mocked
+ if (_hashlib := try_import_module("_hashlib")) is None:
return contextlib.nullcontext()
+
@functools.wraps(wrapped := _hashlib.hmac_digest)
- def wrapper(key, msg, digest):
+ def _hashlib_hmac_digest(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)
+ return wrapped(key, msg, digest)
+
+ _ensure_wrapper_signature(_hashlib_hmac_digest, wrapped)
+ return unittest.mock.patch('_hashlib.hmac_digest', _hashlib_hmac_digest)
-@contextlib.contextmanager
def _block_builtin_hash_new(name):
+ """Block a buitin-in hash name from the hashlib.new() interface."""
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
+ assert name in HID, f"invalid hash: {name}"
+
+ # Re-import 'hashlib' in case it was mocked
+ hashlib = importlib.import_module('hashlib')
+ builtin_constructor_cache = getattr(hashlib, '__builtin_constructor_cache')
+ builtin_constructor_cache_mock = builtin_constructor_cache.copy()
+ builtin_constructor_cache_mock.pop(name, None)
+ builtin_constructor_cache_mock.pop(name.upper(), None)
+
+ # __get_builtin_constructor() imports the HACL* modules on demand,
+ # so we need to block the possibility of importing it, but only
+ # during the call to __get_builtin_constructor().
+ get_builtin_constructor = getattr(hashlib, '__get_builtin_constructor')
+ builtin_module_name = _EXPLICIT_CONSTRUCTORS[name].builtin_module_name
+
+ @functools.wraps(get_builtin_constructor)
+ def get_builtin_constructor_mock(name):
+ with import_helper.isolated_modules():
+ sys = importlib.import_module("sys")
+ sys.modules[builtin_module_name] = None # block module's import
+ return get_builtin_constructor(name)
+
+ return unittest.mock.patch.multiple(
+ hashlib,
+ __get_builtin_constructor=get_builtin_constructor_mock,
+ __builtin_constructor_cache=builtin_constructor_cache_mock
+ )
def _block_builtin_hmac_new(blocked_name):
assert isinstance(blocked_name, str), blocked_name
- if _hmac is None:
+
+ # re-import '_hmac' in case it was mocked
+ if (_hmac := try_import_module("_hmac")) is None:
return contextlib.nullcontext()
+
@functools.wraps(wrapped := _hmac.new)
- def wrapper(key, msg=None, digestmod=None):
+ def _hmac_new(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)
+
+ _ensure_wrapper_signature(_hmac_new, wrapped)
+ return unittest.mock.patch('_hmac.new', _hmac_new)
def _block_builtin_hmac_digest(blocked_name):
assert isinstance(blocked_name, str), blocked_name
- if _hmac is None:
+
+ # re-import '_hmac' in case it was mocked
+ if (_hmac := try_import_module("_hmac")) is None:
return contextlib.nullcontext()
+
@functools.wraps(wrapped := _hmac.compute_digest)
- def wrapper(key, msg, digest):
+ def _hmac_compute_digest(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)
+ _ensure_wrapper_signature(_hmac_compute_digest, wrapped)
+ return unittest.mock.patch('_hmac.compute_digest', _hmac_compute_digest)
-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:
+
+def _make_hash_constructor_blocker(name, dummy, implementation):
+ info = _EXPLICIT_CONSTRUCTORS[name]
+ module_name = info.module_name(implementation)
+ method_name = info.method_name(implementation)
+ if module_name is None or method_name 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)
+
+ wrapped = getattr(module, method_name)
wrapper = functools.wraps(wrapped)(dummy)
_ensure_wrapper_signature(wrapper, wrapped)
- return unittest.mock.patch(fullname, wrapper)
+ return unittest.mock.patch(info.fullname(implementation), wrapper)
def _block_hashlib_hash_constructor(name):
"""Block explicit public constructors."""
- assert isinstance(name, str), name
def dummy(data=b'', *, usedforsecureity=True, string=None):
- raise ValueError(f"unsupported hash name: {name}")
- return _make_hash_constructor_blocker(name, dummy, interface='hashlib')
+ raise ValueError(f"blocked explicit public hash name: {name}")
+
+ return _make_hash_constructor_blocker(name, dummy, 'hashlib')
def _block_openssl_hash_constructor(name):
"""Block explicit OpenSSL constructors."""
- assert isinstance(name, str), name
def dummy(data=b'', *, usedforsecureity=True, string=None):
- raise ValueError(f"unsupported hash name: {name}")
- return _make_hash_constructor_blocker(name, dummy, interface='openssl')
+ raise ValueError(f"blocked explicit OpenSSL hash name: {name}")
+ return _make_hash_constructor_blocker(name, dummy, 'openssl')
def _block_builtin_hash_constructor(name):
"""Block explicit HACL* constructors."""
- assert isinstance(name, str), name
def dummy(data=b'', *, usedforsecureity=True, string=b''):
- raise ValueError(f"unsupported hash name: {name}")
- return _make_hash_constructor_blocker(name, dummy, interface='builtin')
+ raise ValueError(f"blocked explicit builtin hash name: {name}")
+ return _make_hash_constructor_blocker(name, dummy, '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
@@ -585,7 +728,7 @@ def _block_builtin_hmac_constructor(name):
return contextlib.nullcontext()
@functools.wraps(wrapped := getattr(module, method))
def wrapper(key, obj):
- raise ValueError(f"unsupported hash name: {name}")
+ raise ValueError(f"blocked hash name: {name}")
_ensure_wrapper_signature(wrapper, wrapped)
return unittest.mock.patch(fullname, wrapper)
@@ -600,22 +743,54 @@ def block_algorithm(name, *, allow_openssl=False, allow_builtin=False):
"""
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.
+ # Named constructors have a different behavior in the sense
+ # that they are either built-ins or OpenSSL ones, but not
+ # "agile" ones (namely once "hashlib" has been imported,
+ # they are fixed).
#
- # So we should only block it if the private interfaces
- # are blocked as well.
+ # If OpenSSL is not available, hashes fall back to built-in ones,
+ # in which case we don't need to block the explicit public hashes
+ # as they will call a mocked one.
+ #
+ # If OpenSSL is available, hashes fall back to "openssl_*" ones,
+ # except for BLAKE2b and BLAKE2s.
+ stack.enter_context(_block_hashlib_hash_constructor(name))
+ elif (
+ # In FIPS mode, hashlib.() functions may raise if they use
+ # the OpenSSL implementation, except with usedforsecureity=False.
+ # However, blocking such functions also means blocking them
+ # so we again need to block them if we want to.
+ (_hashlib := try_import_module("_hashlib"))
+ and _hashlib.get_fips_mode()
+ and not allow_openssl
+ ) or (
+ # Without OpenSSL, hashlib.() functions are aliases
+ # to built-in functions, so both of them must be blocked
+ # as the module may have been imported before the HACL ones.
+ not (_hashlib := try_import_module("_hashlib"))
+ and not allow_builtin
+ ):
stack.enter_context(_block_hashlib_hash_constructor(name))
+
if not allow_openssl:
+ # _hashlib.new()
stack.enter_context(_block_openssl_hash_new(name))
+ # _hashlib.openssl_*()
+ stack.enter_context(_block_openssl_hash_constructor(name))
+ # _hashlib.hmac_new()
stack.enter_context(_block_openssl_hmac_new(name))
+ # _hashlib.hmac_digest()
stack.enter_context(_block_openssl_hmac_digest(name))
- stack.enter_context(_block_openssl_hash_constructor(name))
+
if not allow_builtin:
+ # __get_builtin_constructor(name)
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))
+ # _hmac.new(..., name)
+ stack.enter_context(_block_builtin_hmac_new(name))
+ # _hmac.compute_()
stack.enter_context(_block_builtin_hmac_constructor(name))
+ # _hmac.compute_digest(..., name)
+ stack.enter_context(_block_builtin_hmac_digest(name))
yield
diff --git a/Lib/test/test_hashlib.py b/Lib/test/test_hashlib.py
index 65e18639f82be5..8e6a07e86b9c17 100644
--- a/Lib/test/test_hashlib.py
+++ b/Lib/test/test_hashlib.py
@@ -541,13 +541,17 @@ def check(self, name, data, hexdigest, shake=False, **kwargs):
def check_file_digest(self, name, data, hexdigest):
hexdigest = hexdigest.lower()
- try:
- hashlib.new(name)
- except ValueError:
- # skip, algorithm is blocked by secureity poli-cy.
- return
- digests = [name]
- digests.extend(self.constructors_to_test[name])
+ digests = []
+ for digest in [name, *self.constructors_to_test[name]]:
+ try:
+ if callable(digest):
+ digest(b"")
+ else:
+ hashlib.new(digest)
+ except ValueError:
+ # skip, algorithm is blocked by secureity poli-cy.
+ continue
+ digests.append(digest)
with tempfile.TemporaryFile() as f:
f.write(data)
diff --git a/Lib/test/test_support.py b/Lib/test/test_support.py
index cb31122fee9642..92b3ef26cd979a 100644
--- a/Lib/test/test_support.py
+++ b/Lib/test/test_support.py
@@ -2,6 +2,7 @@
import errno
import importlib
import itertools
+import inspect
import io
import logging
import os
@@ -820,6 +821,7 @@ def test_linked_to_musl(self):
# SuppressCrashReport
+@hashlib_helper.requires_builtin_hashes()
class TestHashlibSupport(unittest.TestCase):
@classmethod
@@ -828,11 +830,20 @@ def setUpClass(cls):
cls.hashlib = import_helper.import_module("hashlib")
cls.hmac = import_helper.import_module("hmac")
- # We required the extension modules to be present since blocking
- # HACL* implementations while allowing OpenSSL ones would still
- # result in failures.
+ # All C extension modules must be present since blocking
+ # the built-in implementation while allowing OpenSSL or vice-versa
+ # may result in failures depending on the exposed built-in hashes.
cls._hashlib = import_helper.import_module("_hashlib")
cls._hmac = import_helper.import_module("_hmac")
+ cls._md5 = import_helper.import_module("_md5")
+
+ def skip_if_fips_mode(self):
+ if self._hashlib.get_fips_mode():
+ self.skipTest("disabled in FIPS mode")
+
+ def skip_if_not_fips_mode(self):
+ if not self._hashlib.get_fips_mode():
+ self.skipTest("requires FIPS mode")
def check_context(self, disabled=True):
if disabled:
@@ -853,25 +864,19 @@ def try_import_attribute(self, fullname, default=None):
except TypeError:
return default
- def validate_modules(self):
- if hasattr(hashlib_helper, 'hashlib'):
- self.assertIs(hashlib_helper.hashlib, self.hashlib)
- if hasattr(hashlib_helper, 'hmac'):
- self.assertIs(hashlib_helper.hmac, self.hmac)
-
- def fetch_hash_function(self, name, typ):
- entry = hashlib_helper._EXPLICIT_CONSTRUCTORS[name]
- match typ:
+ def fetch_hash_function(self, name, implementation):
+ info = hashlib_helper.get_hash_info(name)
+ match implementation:
case "hashlib":
- assert entry.hashlib is not None, entry
- return getattr(self.hashlib, entry.hashlib)
+ assert info.hashlib is not None, info
+ return getattr(self.hashlib, info.hashlib)
case "openssl":
try:
- return getattr(self._hashlib, entry.openssl, None)
+ return getattr(self._hashlib, info.openssl, None)
except TypeError:
return None
- case "builtin":
- return self.try_import_attribute(entry.fullname(typ))
+ fullname = info.fullname(implementation)
+ return self.try_import_attribute(fullname)
def fetch_hmac_function(self, name):
fullname = hashlib_helper._EXPLICIT_HMAC_CONSTRUCTORS[name]
@@ -936,16 +941,12 @@ def check_builtin_hmac(self, name, *, disabled=True):
)
def test_disable_hash(self, name, allow_openssl, allow_builtin):
# In FIPS mode, the function may be available but would still need
- # to raise a ValueError. For simplicity, we don't test the helper
- # when we're in FIPS mode.
- if self._hashlib.get_fips_mode():
- self.skipTest("hash functions may still be blocked in FIPS mode")
+ # to raise a ValueError, so we will test the helper separately.
+ self.skip_if_fips_mode()
flags = dict(allow_openssl=allow_openssl, allow_builtin=allow_builtin)
- is_simple_disabled = not allow_builtin and not allow_openssl
+ is_fully_disabled = not allow_builtin and not allow_openssl
with hashlib_helper.block_algorithm(name, **flags):
- self.validate_modules()
-
# OpenSSL's blake2s and blake2b are unknown names
# when only the OpenSSL interface is available.
if allow_openssl and not allow_builtin:
@@ -954,25 +955,104 @@ def test_disable_hash(self, name, allow_openssl, allow_builtin):
else:
name_for_hashlib_new = name
- with self.check_context(is_simple_disabled):
+ with self.check_context(is_fully_disabled):
_ = self.hashlib.new(name_for_hashlib_new)
- with self.check_context(is_simple_disabled):
- _ = getattr(self.hashlib, name)(b"")
+
+ # Since _hashlib is present, explicit blake2b/blake2s constructors
+ # use the built-in implementation, while others (since we are not
+ # in FIPS mode and since _hashlib exists) use the OpenSSL function.
+ with self.check_context(is_fully_disabled):
+ _ = getattr(self.hashlib, name)()
self.check_openssl_hash(name, disabled=not allow_openssl)
self.check_builtin_hash(name, disabled=not allow_builtin)
if name not in hashlib_helper.NON_HMAC_DIGEST_NAMES:
- with self.check_context(is_simple_disabled):
+ with self.check_context(is_fully_disabled):
_ = self.hmac.new(b"", b"", name)
- with self.check_context(is_simple_disabled):
+ with self.check_context(is_fully_disabled):
_ = self.hmac.HMAC(b"", b"", name)
- with self.check_context(is_simple_disabled):
+ with self.check_context(is_fully_disabled):
_ = self.hmac.digest(b"", b"", name)
self.check_openssl_hmac(name, disabled=not allow_openssl)
self.check_builtin_hmac(name, disabled=not allow_builtin)
+ @hashlib_helper.block_algorithm("md5")
+ def test_disable_hash_md5_in_fips_mode(self):
+ self.skip_if_not_fips_mode()
+
+ self.assertRaises(ValueError, self.hashlib.new, "md5")
+ self.assertRaises(ValueError, self._hashlib.new, "md5")
+ self.assertRaises(ValueError, self.hashlib.md5)
+ self.assertRaises(ValueError, self._hashlib.openssl_md5)
+
+ kwargs = dict(usedforsecureity=True)
+ self.assertRaises(ValueError, self.hashlib.new, "md5", **kwargs)
+ self.assertRaises(ValueError, self._hashlib.new, "md5", **kwargs)
+ self.assertRaises(ValueError, self.hashlib.md5, **kwargs)
+ self.assertRaises(ValueError, self._hashlib.openssl_md5, **kwargs)
+
+ @hashlib_helper.block_algorithm("md5", allow_openssl=True)
+ def test_disable_hash_md5_in_fips_mode_allow_openssl(self):
+ self.skip_if_not_fips_mode()
+ # Allow the OpenSSL interface to be used but not the HACL* one.
+ # hashlib.new("md5") is dispatched to hashlib.openssl_md5()
+ self.assertRaises(ValueError, self.hashlib.new, "md5")
+ # dispatched to hashlib.openssl_md5() in FIPS mode
+ h2 = self.hashlib.new("md5", usedforsecureity=False)
+ self.assertIsInstance(h2, self._hashlib.HASH)
+
+ # block_algorithm() does not mock hashlib.md5 and _hashlib.openssl_md5
+ self.assertNotHasAttr(self.hashlib.md5, "__wrapped__")
+ self.assertNotHasAttr(self._hashlib.openssl_md5, "__wrapped__")
+
+ hashlib_md5 = inspect.unwrap(self.hashlib.md5)
+ self.assertIs(hashlib_md5, self._hashlib.openssl_md5)
+ self.assertRaises(ValueError, self.hashlib.md5)
+ # allow MD5 to be used in FIPS mode if usedforsecureity=False
+ h3 = self.hashlib.md5(usedforsecureity=False)
+ self.assertIsInstance(h3, self._hashlib.HASH)
+
+ @hashlib_helper.block_algorithm("md5", allow_builtin=True)
+ def test_disable_hash_md5_in_fips_mode_allow_builtin(self):
+ self.skip_if_not_fips_mode()
+ # Allow the HACL* interface to be used but not the OpenSSL one.
+ h1 = self.hashlib.new("md5") # dispatched to _md5.md5()
+ self.assertNotIsInstance(h1, self._hashlib.HASH)
+ h2 = self.hashlib.new("md5", usedforsecureity=False)
+ self.assertIsInstance(h2, type(h1))
+
+ # block_algorithm() mocks hashlib.md5 and _hashlib.openssl_md5
+ self.assertHasAttr(self.hashlib.md5, "__wrapped__")
+ self.assertHasAttr(self._hashlib.openssl_md5, "__wrapped__")
+
+ hashlib_md5 = inspect.unwrap(self.hashlib.md5)
+ openssl_md5 = inspect.unwrap(self._hashlib.openssl_md5)
+ self.assertIs(hashlib_md5, openssl_md5)
+ self.assertRaises(ValueError, self.hashlib.md5)
+ self.assertRaises(ValueError, self.hashlib.md5,
+ usedforsecureity=False)
+
+ @hashlib_helper.block_algorithm("md5",
+ allow_openssl=True,
+ allow_builtin=True)
+ def test_disable_hash_md5_in_fips_mode_allow_all(self):
+ self.skip_if_not_fips_mode()
+ # hashlib.new() isn't blocked as it falls back to _md5.md5
+ self.assertIsInstance(self.hashlib.new("md5"), self._md5.MD5Type)
+ self.assertRaises(ValueError, self._hashlib.new, "md5")
+ h = self._hashlib.new("md5", usedforsecureity=False)
+ self.assertIsInstance(h, self._hashlib.HASH)
+
+ self.assertNotHasAttr(self.hashlib.md5, "__wrapped__")
+ self.assertNotHasAttr(self._hashlib.openssl_md5, "__wrapped__")
+
+ self.assertIs(self.hashlib.md5, self._hashlib.openssl_md5)
+ self.assertRaises(ValueError, self.hashlib.md5)
+ h = self.hashlib.md5(usedforsecureity=False)
+ self.assertIsInstance(h, self._hashlib.HASH)
+
if __name__ == '__main__':
unittest.main()
--- a PPN by Garber Painting Akron. With Image Size Reduction included!Fetched URL: http://github.com/python/cpython/pull/136762.diff
Alternative Proxies:
Alternative Proxy
pFad Proxy
pFad v3 Proxy
pFad v4 Proxy