summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBénédikt Tran <10796600+picnixz@users.noreply.github.com>2025-03-17 10:10:03 (GMT)
committerGitHub <noreply@github.com>2025-03-17 10:10:03 (GMT)
commitde8890f5ab1c1e767029d46c20f513beefc47b18 (patch)
treee1e33cc3247e57c099a2c61f463e7e65efb9ec1e
parent85c04f80fd3a3098674f60038f248c076a476acf (diff)
downloadcpython-de8890f5ab1c1e767029d46c20f513beefc47b18.zip
cpython-de8890f5ab1c1e767029d46c20f513beefc47b18.tar.gz
cpython-de8890f5ab1c1e767029d46c20f513beefc47b18.tar.bz2
gh-130149: cleanup refactorization of `test_hmac.py` (#131318)
New features: * refactor `hashlib_helper.requires_hashdigest` in prevision of a future `hashlib_helper.requires_builtin_hashdigest` for built-in hashes only * add `hashlib_helper.requires_openssl_hashdigest` to request OpenSSL hashes, assuming that `_hashlib` exists. Refactoring: * split hmac.copy() test by implementation * update how algorithms are discovered for RFC test cases * simplify how OpenSSL hash digests are requested * refactor hexdigest tests for RFC test vectors * typo fix: `assert_hmac_hexdigest_by_new` -> `assert_hmac_hexdigest_by_name` Improvements: * strengthen contract on `hmac_new_by_name` and `hmac_digest_by_name` * rename mixin classes to better match their responsibility
-rw-r--r--Lib/hmac.py1
-rw-r--r--Lib/test/support/hashlib_helper.py92
-rw-r--r--Lib/test/test_hmac.py303
3 files changed, 256 insertions, 140 deletions
diff --git a/Lib/hmac.py b/Lib/hmac.py
index 8b4eb2f..30b6b47 100644
--- a/Lib/hmac.py
+++ b/Lib/hmac.py
@@ -65,6 +65,7 @@ class HMAC:
def _init_hmac(self, key, msg, digestmod):
self._hmac = _hashopenssl.hmac_new(key, msg, digestmod=digestmod)
+ self._inner = self._outer = None # because the slots are defined
self.digest_size = self._hmac.digest_size
self.block_size = self._hmac.block_size
diff --git a/Lib/test/support/hashlib_helper.py b/Lib/test/support/hashlib_helper.py
index 477e0f1..bed3d69 100644
--- a/Lib/test/support/hashlib_helper.py
+++ b/Lib/test/support/hashlib_helper.py
@@ -1,6 +1,7 @@
import functools
import hashlib
import unittest
+from test.support.import_helper import import_module
try:
import _hashlib
@@ -12,44 +13,81 @@ def requires_hashlib():
return unittest.skipIf(_hashlib is None, "requires _hashlib")
+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
+
+
def requires_hashdigest(digestname, openssl=None, usedforsecurity=True):
- """Decorator raising SkipTest if a hashing algorithm is not available
+ """Decorator raising SkipTest if a hashing algorithm is not available.
- The hashing algorithm could be missing or blocked by a strict crypto
- policy.
+ The hashing algorithm may be missing, blocked by a strict crypto policy,
+ or Python may be configured with `--with-builtin-hashlib-hashes=no`.
If 'openssl' is True, then the decorator checks that OpenSSL provides
- the algorithm. Otherwise the check falls back to built-in
- implementations. The usedforsecurity flag is passed to the constructor.
+ 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.
+ Examples of exceptions being suppressed:
ValueError: [digital envelope routines: EVP_DigestInit_ex] disabled for FIPS
ValueError: unsupported hash type md4
"""
+ 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:
+ msg = f"missing hash algorithm: {digestname!r}"
+ raise unittest.SkipTest(msg) from exc
+ return func(*args, **kwargs)
+ return wrapper
+
def decorator(func_or_class):
- if isinstance(func_or_class, type):
- setUpClass = func_or_class.__dict__.get('setUpClass')
- if setUpClass is None:
- def setUpClass(cls):
- super(func_or_class, cls).setUpClass()
- setUpClass.__qualname__ = func_or_class.__qualname__ + '.setUpClass'
- setUpClass.__module__ = func_or_class.__module__
- else:
- setUpClass = setUpClass.__func__
- setUpClass = classmethod(decorator(setUpClass))
- func_or_class.setUpClass = setUpClass
- return func_or_class
-
- @functools.wraps(func_or_class)
+ 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.
+ """
+ def decorator_func(func):
+ @requires_hashlib()
+ @functools.wraps(func)
def wrapper(*args, **kwargs):
try:
- if openssl and _hashlib is not None:
- _hashlib.new(digestname, usedforsecurity=usedforsecurity)
- else:
- hashlib.new(digestname, usedforsecurity=usedforsecurity)
+ _hashlib.new(digestname, usedforsecurity=usedforsecurity)
except ValueError:
- raise unittest.SkipTest(
- f"hash digest {digestname!r} is not available."
- )
- return func_or_class(*args, **kwargs)
+ msg = f"missing OpenSSL hash algorithm: {digestname!r}"
+ raise unittest.SkipTest(msg)
+ return func(*args, **kwargs)
return wrapper
+
+ def decorator(func_or_class):
+ return _decorate_func_or_class(func_or_class, decorator_func)
return decorator
diff --git a/Lib/test/test_hmac.py b/Lib/test/test_hmac.py
index c8c806d..ed96af2 100644
--- a/Lib/test/test_hmac.py
+++ b/Lib/test/test_hmac.py
@@ -61,7 +61,11 @@ class CreatorMixin:
"""Mixin exposing a method creating a HMAC object."""
def hmac_new(self, key, msg=None, digestmod=None):
- """Create a new HMAC object."""
+ """Create a new HMAC object.
+
+ Implementations should accept arbitrary 'digestmod' as this
+ method can be used to test which exceptions are being raised.
+ """
raise NotImplementedError
def bind_hmac_new(self, digestmod):
@@ -73,7 +77,11 @@ class DigestMixin:
"""Mixin exposing a method computing a HMAC digest."""
def hmac_digest(self, key, msg=None, digestmod=None):
- """Compute a HMAC digest."""
+ """Compute a HMAC digest.
+
+ Implementations should accept arbitrary 'digestmod' as this
+ method can be used to test which exceptions are being raised.
+ """
raise NotImplementedError
def bind_hmac_digest(self, digestmod):
@@ -120,7 +128,7 @@ class ThroughOpenSSLAPIMixin(CreatorMixin, DigestMixin):
return _hashlib.hmac_digest(key, msg, digest=digestmod)
-class CheckerMixin:
+class ObjectCheckerMixin:
"""Mixin for checking HMAC objects (pure Python, OpenSSL or built-in)."""
def check_object(self, h, hexdigest, hashname, digest_size, block_size):
@@ -141,10 +149,10 @@ class CheckerMixin:
self.assertEqual(h.hexdigest().upper(), hexdigest.upper())
-class TestVectorsMixin(CreatorMixin, DigestMixin, CheckerMixin):
- """Mixin class for all test vectors test cases."""
+class AssertersMixin(CreatorMixin, DigestMixin, ObjectCheckerMixin):
+ """Mixin class for common tests."""
- def hmac_new_by_name(self, key, msg=None, hashname=None):
+ def hmac_new_by_name(self, key, msg=None, *, hashname):
"""Alternative implementation of hmac_new().
This is typically useful when one needs to test against an HMAC
@@ -152,13 +160,22 @@ class TestVectorsMixin(CreatorMixin, DigestMixin, CheckerMixin):
by their name (all HMAC implementations must at least recognize
hash functions by their names but some may use aliases such as
`hashlib.sha1` instead of "sha1").
+
+ Unlike hmac_new(), this method may assert the type of 'hashname'
+ as it should only be used in tests that are expected to create
+ a HMAC object.
"""
- self.assertIsInstance(hashname, str | None)
+ self.assertIsInstance(hashname, str)
return self.hmac_new(key, msg, digestmod=hashname)
- def hmac_digest_by_name(self, key, msg=None, hashname=None):
- """Alternative implementation of hmac_digest()."""
- self.assertIsInstance(hashname, str | None)
+ def hmac_digest_by_name(self, key, msg=None, *, hashname):
+ """Alternative implementation of hmac_digest().
+
+ Unlike hmac_digest(), this method may assert the type of 'hashname'
+ as it should only be used in tests that are expected to compute a
+ HMAC digest.
+ """
+ self.assertIsInstance(hashname, str)
return self.hmac_digest(key, msg, digestmod=hashname)
def assert_hmac(
@@ -196,7 +213,7 @@ class TestVectorsMixin(CreatorMixin, DigestMixin, CheckerMixin):
self.assert_hmac_new_by_name(
key, msg, hexdigest, hashname, digest_size, block_size
)
- self.assert_hmac_hexdigest_by_new(
+ self.assert_hmac_hexdigest_by_name(
key, msg, hexdigest, hashname, digest_size
)
@@ -255,16 +272,28 @@ class TestVectorsMixin(CreatorMixin, DigestMixin, CheckerMixin):
self, key, msg, hexdigest, digestmod, digest_size,
):
"""Check a HMAC digest computed by hmac_digest()."""
- d = self.hmac_digest(key, msg, digestmod=digestmod)
- self.assertEqual(len(d), digest_size)
- self.assertEqual(d, binascii.unhexlify(hexdigest))
+ self._check_hmac_hexdigest(
+ key, msg, hexdigest, digest_size,
+ hmac_digest_func=self.hmac_digest,
+ hmac_digest_kwds={'digestmod': digestmod},
+ )
- def assert_hmac_hexdigest_by_new(
+ def assert_hmac_hexdigest_by_name(
self, key, msg, hexdigest, hashname, digest_size
):
"""Check a HMAC digest computed by hmac_digest_by_name()."""
- self.assertIsInstance(hashname, str | None)
- d = self.hmac_digest_by_name(key, msg, hashname=hashname)
+ self.assertIsInstance(hashname, str)
+ self._check_hmac_hexdigest(
+ key, msg, hexdigest, digest_size,
+ hmac_digest_func=self.hmac_digest_by_name,
+ hmac_digest_kwds={'hashname': hashname},
+ )
+
+ def _check_hmac_hexdigest(
+ self, key, msg, hexdigest, digest_size,
+ hmac_digest_func, hmac_digest_kwds,
+ ):
+ d = hmac_digest_func(key, msg, **hmac_digest_kwds)
self.assertEqual(len(d), digest_size)
self.assertEqual(d, binascii.unhexlify(hexdigest))
@@ -279,7 +308,7 @@ class TestVectorsMixin(CreatorMixin, DigestMixin, CheckerMixin):
self.check_object(h1, hexdigest, hashname, digest_size, block_size)
-class PyTestVectorsMixin(PyModuleMixin, TestVectorsMixin):
+class PyAssertersMixin(PyModuleMixin, AssertersMixin):
def assert_hmac_extra_cases(
self, key, msg, hexdigest, digestmod, hashname, digest_size, block_size
@@ -293,46 +322,62 @@ class PyTestVectorsMixin(PyModuleMixin, TestVectorsMixin):
self.check_object(h, hexdigest, hashname, digest_size, block_size)
-class OpenSSLTestVectorsMixin(TestVectorsMixin):
-
- def hmac_new(self, key, msg=None, digestmod=None):
- return _hashlib.hmac_new(key, msg, digestmod=digestmod)
-
- def hmac_digest(self, key, msg=None, digestmod=None):
- return _hashlib.hmac_digest(key, msg, digest=digestmod)
+class OpenSSLAssertersMixin(ThroughOpenSSLAPIMixin, AssertersMixin):
- def hmac_new_by_name(self, key, msg=None, hashname=None):
- # ignore 'digestmod' and use the exact openssl function
+ def hmac_new_by_name(self, key, msg=None, *, hashname):
+ self.assertIsInstance(hashname, str)
openssl_func = getattr(_hashlib, f"openssl_{hashname}")
return self.hmac_new(key, msg, digestmod=openssl_func)
- def hmac_digest_by_name(self, key, msg=None, hashname=None):
+ def hmac_digest_by_name(self, key, msg=None, *, hashname):
+ self.assertIsInstance(hashname, str)
openssl_func = getattr(_hashlib, f"openssl_{hashname}")
return self.hmac_digest(key, msg, digestmod=openssl_func)
-class RFCTestCasesMixin(TestVectorsMixin):
- """Test HMAC implementations against test vectors from the RFC.
-
- Subclasses must override the 'md5' and other 'sha*' attributes
- to test the implementations. Their value can be a string, a callable,
- or a PEP-257 module.
- """
+class HashFunctionsTrait:
+ """Trait class for 'hashfunc' in hmac_new() and hmac_digest()."""
ALGORITHMS = [
'md5', 'sha1',
'sha224', 'sha256', 'sha384', 'sha512',
]
- # Those will be automatically set to non-None on subclasses
- # as they are set by __init_subclass()__.
- md5 = sha1 = sha224 = sha256 = sha384 = sha512 = None
+ # By default, a missing algorithm skips the test that uses it.
+ md5 = sha1 = sha224 = sha256 = sha384 = sha512 = property(
+ lambda self: self.skipTest("missing hash function")
+ )
+
+
+class WithOpenSSLHashFunctions(HashFunctionsTrait):
+ """Test a HMAC implementation with an OpenSSL-based callable 'hashfunc'."""
+
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+
+ for name in cls.ALGORITHMS:
+ @property
+ @hashlib_helper.requires_openssl_hashdigest(name)
+ def func(self, *, __name=name): # __name needed to bind 'name'
+ return getattr(_hashlib, f'openssl_{__name}')
+ setattr(cls, name, func)
+
+
+class WithNamedHashFunctions(HashFunctionsTrait):
+ """Test a HMAC implementation with a named 'hashfunc'."""
+
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
- def __init_subclass__(cls, *args, **kwargs):
- super().__init_subclass__(*args, **kwargs)
for name in cls.ALGORITHMS:
setattr(cls, name, name)
+
+class RFCTestCaseMixin(HashFunctionsTrait):
+ """Test HMAC implementations against test vectors from the RFC."""
+
def test_md5(self):
def md5test(key, msg, hexdigest):
self.assert_hmac(key, msg, hexdigest, self.md5, "md5", 16, 64)
@@ -412,7 +457,6 @@ class RFCTestCasesMixin(TestVectorsMixin):
self._test_sha2_rfc4231(self.sha512, 'sha512', 64, 128)
def _test_sha2_rfc4231(self, hashfunc, hashname, digest_size, block_size):
-
def hmactest(key, data, hexdigests):
hexdigest = hexdigests[hashname]
@@ -531,57 +575,9 @@ class RFCTestCasesMixin(TestVectorsMixin):
'134676fb6de0446065c97440fa8c6a58',
})
- @hashlib_helper.requires_hashdigest('sha256')
- def test_legacy_block_size_warnings(self):
- class MockCrazyHash(object):
- """Ain't no block_size attribute here."""
- def __init__(self, *args):
- self._x = hashlib.sha256(*args)
- self.digest_size = self._x.digest_size
- def update(self, v):
- self._x.update(v)
- def digest(self):
- return self._x.digest()
-
- with warnings.catch_warnings():
- warnings.simplefilter('error', RuntimeWarning)
- with self.assertRaises(RuntimeWarning):
- hmac.HMAC(b'a', b'b', digestmod=MockCrazyHash)
- self.fail('Expected warning about missing block_size')
-
- MockCrazyHash.block_size = 1
- with self.assertRaises(RuntimeWarning):
- hmac.HMAC(b'a', b'b', digestmod=MockCrazyHash)
- self.fail('Expected warning about small block_size')
-
- def test_with_fallback(self):
- cache = getattr(hashlib, '__builtin_constructor_cache')
- try:
- cache['foo'] = hashlib.sha256
- hexdigest = hmac.digest(b'key', b'message', 'foo').hex()
- expected = ('6e9ef29b75fffc5b7abae527d58fdadb'
- '2fe42e7219011976917343065f58ed4a')
- self.assertEqual(hexdigest, expected)
- finally:
- cache.pop('foo')
-
-class RFCWithOpenSSLHashFunctionTestCasesMixin(RFCTestCasesMixin):
-
- def __init_subclass__(cls, *args, **kwargs):
- super().__init_subclass__(*args, **kwargs)
-
- for name in cls.ALGORITHMS:
- @property
- @hashlib_helper.requires_hashlib()
- @hashlib_helper.requires_hashdigest(name, openssl=True)
- def func(self, *, __name=name): # __name needed to bind 'name'
- return getattr(_hashlib, f'openssl_{__name}')
- setattr(cls, name, func)
-
-
-class PyRFCTestCase(PyTestVectorsMixin, ThroughObjectMixin,
- RFCWithOpenSSLHashFunctionTestCasesMixin,
+class PyRFCTestCase(ThroughObjectMixin, PyAssertersMixin,
+ WithOpenSSLHashFunctions, RFCTestCaseMixin,
unittest.TestCase):
"""Python implementation of HMAC using hmac.HMAC().
@@ -589,8 +585,8 @@ class PyRFCTestCase(PyTestVectorsMixin, ThroughObjectMixin,
"""
-class PyDotNewRFCTestCase(PyTestVectorsMixin, ThroughModuleAPIMixin,
- RFCWithOpenSSLHashFunctionTestCasesMixin,
+class PyDotNewRFCTestCase(ThroughModuleAPIMixin, PyAssertersMixin,
+ WithOpenSSLHashFunctions, RFCTestCaseMixin,
unittest.TestCase):
"""Python implementation of HMAC using hmac.new().
@@ -598,12 +594,13 @@ class PyDotNewRFCTestCase(PyTestVectorsMixin, ThroughModuleAPIMixin,
"""
-class OpenSSLRFCTestCase(OpenSSLTestVectorsMixin,
- RFCWithOpenSSLHashFunctionTestCasesMixin,
+class OpenSSLRFCTestCase(OpenSSLAssertersMixin,
+ WithOpenSSLHashFunctions, RFCTestCaseMixin,
unittest.TestCase):
"""OpenSSL implementation of HMAC.
- The underlying hash functions are also OpenSSL-based."""
+ The underlying hash functions are also OpenSSL-based.
+ """
# TODO(picnixz): once we have a HACL* HMAC, we should also test the Python
@@ -668,7 +665,7 @@ class DigestModTestCaseMixin(CreatorMixin, DigestMixin):
return cases
-class ConstructorTestCaseMixin(CreatorMixin, DigestMixin, CheckerMixin):
+class ConstructorTestCaseMixin(CreatorMixin, DigestMixin, ObjectCheckerMixin):
"""HMAC constructor tests based on HMAC-SHA-2/256."""
key = b"key"
@@ -847,7 +844,7 @@ class PySanityTestCase(ThroughObjectMixin, PyModuleMixin, SanityTestCaseMixin,
self.assertStartsWith(repr(h), "<hmac.HMAC object at")
-@hashlib_helper.requires_hashdigest('sha256', openssl=True)
+@hashlib_helper.requires_openssl_hashdigest('sha256')
class OpenSSLSanityTestCase(ThroughOpenSSLAPIMixin, SanityTestCaseMixin,
unittest.TestCase):
@@ -899,45 +896,50 @@ class PyUpdateTestCase(UpdateTestCaseMixin, unittest.TestCase):
return self.hmac.HMAC(key, msg, digestmod='sha256')
-@hashlib_helper.requires_hashlib()
-@hashlib_helper.requires_hashdigest('sha256', openssl=True)
+@hashlib_helper.requires_openssl_hashdigest('sha256')
class OpenSSLUpdateTestCase(UpdateTestCaseMixin, unittest.TestCase):
def HMAC(self, key, msg=None):
return hmac.new(key, msg, digestmod='sha256')
+class CopyBaseTestCase:
+
+ def test_attributes(self):
+ raise NotImplementedError
+
+ def test_realcopy(self):
+ raise NotImplementedError
+
+
@hashlib_helper.requires_hashdigest('sha256')
-class CopyTestCase(unittest.TestCase):
+class PythonCopyTestCase(CopyBaseTestCase, unittest.TestCase):
- def test_attributes_old(self):
+ def test_attributes(self):
# Testing if attributes are of same type.
h1 = hmac.HMAC.__new__(hmac.HMAC)
h1._init_old(b"key", b"msg", digestmod="sha256")
+ self.assertIsNone(h1._hmac)
+ self.assertIsNotNone(h1._inner)
+ self.assertIsNotNone(h1._outer)
+
h2 = h1.copy()
+ self.assertIsNone(h2._hmac)
+ self.assertIsNotNone(h2._inner)
+ self.assertIsNotNone(h2._outer)
self.assertEqual(type(h1._inner), type(h2._inner))
self.assertEqual(type(h1._outer), type(h2._outer))
- def test_realcopy_old(self):
+ def test_realcopy(self):
# Testing if the copy method created a real copy.
h1 = hmac.HMAC.__new__(hmac.HMAC)
h1._init_old(b"key", b"msg", digestmod="sha256")
- self.assertIsNone(h1._hmac)
-
h2 = h1.copy()
- self.assertIsNone(h2._hmac)
# Using id() in case somebody has overridden __eq__/__ne__.
self.assertNotEqual(id(h1), id(h2))
self.assertNotEqual(id(h1._inner), id(h2._inner))
self.assertNotEqual(id(h1._outer), id(h2._outer))
- @hashlib_helper.requires_hashlib()
- def test_realcopy_hmac(self):
- h1 = hmac.HMAC.__new__(hmac.HMAC)
- h1._init_hmac(b"key", b"msg", digestmod="sha256")
- h2 = h1.copy()
- self.assertNotEqual(id(h1._hmac), id(h2._hmac))
-
def test_equality(self):
# Testing if the copy has the same digests.
h1 = hmac.HMAC(b"key", digestmod="sha256")
@@ -951,11 +953,47 @@ class CopyTestCase(unittest.TestCase):
h1 = hmac.new(b"key", digestmod="sha256")
h1.update(b"some random text")
h2 = h1.copy()
+ # Using id() in case somebody has overridden __eq__/__ne__.
self.assertNotEqual(id(h1), id(h2))
self.assertEqual(h1.digest(), h2.digest())
self.assertEqual(h1.hexdigest(), h2.hexdigest())
+class ExtensionCopyTestCase(CopyBaseTestCase):
+
+ def init(self, h):
+ """Call the dedicate init() method to test."""
+ raise NotImplementedError
+
+ def test_attributes(self):
+ # Testing if attributes are of same type.
+ h1 = hmac.HMAC.__new__(hmac.HMAC)
+
+ self.init(h1)
+ self.assertIsNotNone(h1._hmac)
+ self.assertIsNone(h1._inner)
+ self.assertIsNone(h1._outer)
+
+ h2 = h1.copy()
+ self.assertIsNotNone(h2._hmac)
+ self.assertIsNone(h2._inner)
+ self.assertIsNone(h2._outer)
+
+ def test_realcopy(self):
+ h1 = hmac.HMAC.__new__(hmac.HMAC)
+ self.init(h1)
+ h2 = h1.copy()
+ # Using id() in case somebody has overridden __eq__/__ne__.
+ self.assertNotEqual(id(h1._hmac), id(h2._hmac))
+
+
+@hashlib_helper.requires_openssl_hashdigest('sha256')
+class OpenSSLCopyTestCase(ExtensionCopyTestCase, unittest.TestCase):
+
+ def init(self, h):
+ h._init_hmac(b"key", b"msg", digestmod="sha256")
+
+
class CompareDigestMixin:
@staticmethod
@@ -1087,5 +1125,44 @@ class OperatorCompareDigestTestCase(CompareDigestMixin, unittest.TestCase):
compare_digest = operator_compare_digest
+class PyMiscellaneousTests(unittest.TestCase):
+ """Miscellaneous tests for the pure Python HMAC module."""
+
+ @hashlib_helper.requires_hashdigest('sha256')
+ def test_legacy_block_size_warnings(self):
+ class MockCrazyHash(object):
+ """Ain't no block_size attribute here."""
+ def __init__(self, *args):
+ self._x = hashlib.sha256(*args)
+ self.digest_size = self._x.digest_size
+ def update(self, v):
+ self._x.update(v)
+ def digest(self):
+ return self._x.digest()
+
+ with warnings.catch_warnings():
+ warnings.simplefilter('error', RuntimeWarning)
+ with self.assertRaises(RuntimeWarning):
+ hmac.HMAC(b'a', b'b', digestmod=MockCrazyHash)
+ self.fail('Expected warning about missing block_size')
+
+ MockCrazyHash.block_size = 1
+ with self.assertRaises(RuntimeWarning):
+ hmac.HMAC(b'a', b'b', digestmod=MockCrazyHash)
+ self.fail('Expected warning about small block_size')
+
+ @hashlib_helper.requires_hashdigest('sha256')
+ def test_with_fallback(self):
+ cache = getattr(hashlib, '__builtin_constructor_cache')
+ try:
+ cache['foo'] = hashlib.sha256
+ hexdigest = hmac.digest(b'key', b'message', 'foo').hex()
+ expected = ('6e9ef29b75fffc5b7abae527d58fdadb'
+ '2fe42e7219011976917343065f58ed4a')
+ self.assertEqual(hexdigest, expected)
+ finally:
+ cache.pop('foo')
+
+
if __name__ == "__main__":
unittest.main()