mirror of
https://github.com/python/cpython.git
synced 2025-09-09 18:32:22 +00:00
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
This commit is contained in:
parent
85c04f80fd
commit
de8890f5ab
3 changed files with 263 additions and 147 deletions
|
@ -65,6 +65,7 @@ class HMAC:
|
||||||
|
|
||||||
def _init_hmac(self, key, msg, digestmod):
|
def _init_hmac(self, key, msg, digestmod):
|
||||||
self._hmac = _hashopenssl.hmac_new(key, msg, digestmod=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.digest_size = self._hmac.digest_size
|
||||||
self.block_size = self._hmac.block_size
|
self.block_size = self._hmac.block_size
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import functools
|
import functools
|
||||||
import hashlib
|
import hashlib
|
||||||
import unittest
|
import unittest
|
||||||
|
from test.support.import_helper import import_module
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import _hashlib
|
import _hashlib
|
||||||
|
@ -12,44 +13,81 @@ def requires_hashlib():
|
||||||
return unittest.skipIf(_hashlib is None, "requires _hashlib")
|
return unittest.skipIf(_hashlib is None, "requires _hashlib")
|
||||||
|
|
||||||
|
|
||||||
def requires_hashdigest(digestname, openssl=None, usedforsecurity=True):
|
def _decorate_func_or_class(func_or_class, decorator_func):
|
||||||
"""Decorator raising SkipTest if a hashing algorithm is not available
|
if not isinstance(func_or_class, type):
|
||||||
|
return decorator_func(func_or_class)
|
||||||
|
|
||||||
The hashing algorithm could be missing or blocked by a strict crypto
|
decorated_class = func_or_class
|
||||||
policy.
|
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.
|
||||||
|
|
||||||
|
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
|
If 'openssl' is True, then the decorator checks that OpenSSL provides
|
||||||
the algorithm. Otherwise the check falls back to built-in
|
the algorithm. Otherwise the check falls back to (optional) built-in
|
||||||
implementations. The usedforsecurity flag is passed to the constructor.
|
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: [digital envelope routines: EVP_DigestInit_ex] disabled for FIPS
|
||||||
ValueError: unsupported hash type md4
|
ValueError: unsupported hash type md4
|
||||||
"""
|
"""
|
||||||
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)
|
|
||||||
def wrapper(*args, **kwargs):
|
|
||||||
try:
|
|
||||||
if openssl and _hashlib is not None:
|
if openssl and _hashlib is not None:
|
||||||
|
def test_availability():
|
||||||
_hashlib.new(digestname, usedforsecurity=usedforsecurity)
|
_hashlib.new(digestname, usedforsecurity=usedforsecurity)
|
||||||
else:
|
else:
|
||||||
|
def test_availability():
|
||||||
hashlib.new(digestname, usedforsecurity=usedforsecurity)
|
hashlib.new(digestname, usedforsecurity=usedforsecurity)
|
||||||
except ValueError:
|
|
||||||
raise unittest.SkipTest(
|
def decorator_func(func):
|
||||||
f"hash digest {digestname!r} is not available."
|
@functools.wraps(func)
|
||||||
)
|
def wrapper(*args, **kwargs):
|
||||||
return func_or_class(*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
|
return wrapper
|
||||||
|
|
||||||
|
def decorator(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:
|
||||||
|
_hashlib.new(digestname, usedforsecurity=usedforsecurity)
|
||||||
|
except ValueError:
|
||||||
|
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
|
return decorator
|
||||||
|
|
|
@ -61,7 +61,11 @@ class CreatorMixin:
|
||||||
"""Mixin exposing a method creating a HMAC object."""
|
"""Mixin exposing a method creating a HMAC object."""
|
||||||
|
|
||||||
def hmac_new(self, key, msg=None, digestmod=None):
|
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
|
raise NotImplementedError
|
||||||
|
|
||||||
def bind_hmac_new(self, digestmod):
|
def bind_hmac_new(self, digestmod):
|
||||||
|
@ -73,7 +77,11 @@ class DigestMixin:
|
||||||
"""Mixin exposing a method computing a HMAC digest."""
|
"""Mixin exposing a method computing a HMAC digest."""
|
||||||
|
|
||||||
def hmac_digest(self, key, msg=None, digestmod=None):
|
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
|
raise NotImplementedError
|
||||||
|
|
||||||
def bind_hmac_digest(self, digestmod):
|
def bind_hmac_digest(self, digestmod):
|
||||||
|
@ -120,7 +128,7 @@ class ThroughOpenSSLAPIMixin(CreatorMixin, DigestMixin):
|
||||||
return _hashlib.hmac_digest(key, msg, digest=digestmod)
|
return _hashlib.hmac_digest(key, msg, digest=digestmod)
|
||||||
|
|
||||||
|
|
||||||
class CheckerMixin:
|
class ObjectCheckerMixin:
|
||||||
"""Mixin for checking HMAC objects (pure Python, OpenSSL or built-in)."""
|
"""Mixin for checking HMAC objects (pure Python, OpenSSL or built-in)."""
|
||||||
|
|
||||||
def check_object(self, h, hexdigest, hashname, digest_size, block_size):
|
def check_object(self, h, hexdigest, hashname, digest_size, block_size):
|
||||||
|
@ -141,10 +149,10 @@ class CheckerMixin:
|
||||||
self.assertEqual(h.hexdigest().upper(), hexdigest.upper())
|
self.assertEqual(h.hexdigest().upper(), hexdigest.upper())
|
||||||
|
|
||||||
|
|
||||||
class TestVectorsMixin(CreatorMixin, DigestMixin, CheckerMixin):
|
class AssertersMixin(CreatorMixin, DigestMixin, ObjectCheckerMixin):
|
||||||
"""Mixin class for all test vectors test cases."""
|
"""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().
|
"""Alternative implementation of hmac_new().
|
||||||
|
|
||||||
This is typically useful when one needs to test against an HMAC
|
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
|
by their name (all HMAC implementations must at least recognize
|
||||||
hash functions by their names but some may use aliases such as
|
hash functions by their names but some may use aliases such as
|
||||||
`hashlib.sha1` instead of "sha1").
|
`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)
|
return self.hmac_new(key, msg, digestmod=hashname)
|
||||||
|
|
||||||
def hmac_digest_by_name(self, key, msg=None, hashname=None):
|
def hmac_digest_by_name(self, key, msg=None, *, hashname):
|
||||||
"""Alternative implementation of hmac_digest()."""
|
"""Alternative implementation of hmac_digest().
|
||||||
self.assertIsInstance(hashname, str | None)
|
|
||||||
|
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)
|
return self.hmac_digest(key, msg, digestmod=hashname)
|
||||||
|
|
||||||
def assert_hmac(
|
def assert_hmac(
|
||||||
|
@ -196,7 +213,7 @@ class TestVectorsMixin(CreatorMixin, DigestMixin, CheckerMixin):
|
||||||
self.assert_hmac_new_by_name(
|
self.assert_hmac_new_by_name(
|
||||||
key, msg, hexdigest, hashname, digest_size, block_size
|
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
|
key, msg, hexdigest, hashname, digest_size
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -255,16 +272,28 @@ class TestVectorsMixin(CreatorMixin, DigestMixin, CheckerMixin):
|
||||||
self, key, msg, hexdigest, digestmod, digest_size,
|
self, key, msg, hexdigest, digestmod, digest_size,
|
||||||
):
|
):
|
||||||
"""Check a HMAC digest computed by hmac_digest()."""
|
"""Check a HMAC digest computed by hmac_digest()."""
|
||||||
d = self.hmac_digest(key, msg, digestmod=digestmod)
|
self._check_hmac_hexdigest(
|
||||||
self.assertEqual(len(d), digest_size)
|
key, msg, hexdigest, digest_size,
|
||||||
self.assertEqual(d, binascii.unhexlify(hexdigest))
|
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
|
self, key, msg, hexdigest, hashname, digest_size
|
||||||
):
|
):
|
||||||
"""Check a HMAC digest computed by hmac_digest_by_name()."""
|
"""Check a HMAC digest computed by hmac_digest_by_name()."""
|
||||||
self.assertIsInstance(hashname, str | None)
|
self.assertIsInstance(hashname, str)
|
||||||
d = self.hmac_digest_by_name(key, msg, hashname=hashname)
|
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(len(d), digest_size)
|
||||||
self.assertEqual(d, binascii.unhexlify(hexdigest))
|
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)
|
self.check_object(h1, hexdigest, hashname, digest_size, block_size)
|
||||||
|
|
||||||
|
|
||||||
class PyTestVectorsMixin(PyModuleMixin, TestVectorsMixin):
|
class PyAssertersMixin(PyModuleMixin, AssertersMixin):
|
||||||
|
|
||||||
def assert_hmac_extra_cases(
|
def assert_hmac_extra_cases(
|
||||||
self, key, msg, hexdigest, digestmod, hashname, digest_size, block_size
|
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)
|
self.check_object(h, hexdigest, hashname, digest_size, block_size)
|
||||||
|
|
||||||
|
|
||||||
class OpenSSLTestVectorsMixin(TestVectorsMixin):
|
class OpenSSLAssertersMixin(ThroughOpenSSLAPIMixin, AssertersMixin):
|
||||||
|
|
||||||
def hmac_new(self, key, msg=None, digestmod=None):
|
def hmac_new_by_name(self, key, msg=None, *, hashname):
|
||||||
return _hashlib.hmac_new(key, msg, digestmod=digestmod)
|
self.assertIsInstance(hashname, str)
|
||||||
|
|
||||||
def hmac_digest(self, key, msg=None, digestmod=None):
|
|
||||||
return _hashlib.hmac_digest(key, msg, digest=digestmod)
|
|
||||||
|
|
||||||
def hmac_new_by_name(self, key, msg=None, hashname=None):
|
|
||||||
# ignore 'digestmod' and use the exact openssl function
|
|
||||||
openssl_func = getattr(_hashlib, f"openssl_{hashname}")
|
openssl_func = getattr(_hashlib, f"openssl_{hashname}")
|
||||||
return self.hmac_new(key, msg, digestmod=openssl_func)
|
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}")
|
openssl_func = getattr(_hashlib, f"openssl_{hashname}")
|
||||||
return self.hmac_digest(key, msg, digestmod=openssl_func)
|
return self.hmac_digest(key, msg, digestmod=openssl_func)
|
||||||
|
|
||||||
|
|
||||||
class RFCTestCasesMixin(TestVectorsMixin):
|
class HashFunctionsTrait:
|
||||||
"""Test HMAC implementations against test vectors from the RFC.
|
"""Trait class for 'hashfunc' in hmac_new() and hmac_digest()."""
|
||||||
|
|
||||||
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.
|
|
||||||
"""
|
|
||||||
|
|
||||||
ALGORITHMS = [
|
ALGORITHMS = [
|
||||||
'md5', 'sha1',
|
'md5', 'sha1',
|
||||||
'sha224', 'sha256', 'sha384', 'sha512',
|
'sha224', 'sha256', 'sha384', 'sha512',
|
||||||
]
|
]
|
||||||
|
|
||||||
# Those will be automatically set to non-None on subclasses
|
# By default, a missing algorithm skips the test that uses it.
|
||||||
# as they are set by __init_subclass()__.
|
md5 = sha1 = sha224 = sha256 = sha384 = sha512 = property(
|
||||||
md5 = sha1 = sha224 = sha256 = sha384 = sha512 = None
|
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:
|
for name in cls.ALGORITHMS:
|
||||||
setattr(cls, name, name)
|
setattr(cls, name, name)
|
||||||
|
|
||||||
|
|
||||||
|
class RFCTestCaseMixin(HashFunctionsTrait):
|
||||||
|
"""Test HMAC implementations against test vectors from the RFC."""
|
||||||
|
|
||||||
def test_md5(self):
|
def test_md5(self):
|
||||||
def md5test(key, msg, hexdigest):
|
def md5test(key, msg, hexdigest):
|
||||||
self.assert_hmac(key, msg, hexdigest, self.md5, "md5", 16, 64)
|
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)
|
self._test_sha2_rfc4231(self.sha512, 'sha512', 64, 128)
|
||||||
|
|
||||||
def _test_sha2_rfc4231(self, hashfunc, hashname, digest_size, block_size):
|
def _test_sha2_rfc4231(self, hashfunc, hashname, digest_size, block_size):
|
||||||
|
|
||||||
def hmactest(key, data, hexdigests):
|
def hmactest(key, data, hexdigests):
|
||||||
hexdigest = hexdigests[hashname]
|
hexdigest = hexdigests[hashname]
|
||||||
|
|
||||||
|
@ -531,57 +575,9 @@ class RFCTestCasesMixin(TestVectorsMixin):
|
||||||
'134676fb6de0446065c97440fa8c6a58',
|
'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():
|
class PyRFCTestCase(ThroughObjectMixin, PyAssertersMixin,
|
||||||
warnings.simplefilter('error', RuntimeWarning)
|
WithOpenSSLHashFunctions, RFCTestCaseMixin,
|
||||||
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,
|
|
||||||
unittest.TestCase):
|
unittest.TestCase):
|
||||||
"""Python implementation of HMAC using hmac.HMAC().
|
"""Python implementation of HMAC using hmac.HMAC().
|
||||||
|
|
||||||
|
@ -589,8 +585,8 @@ class PyRFCTestCase(PyTestVectorsMixin, ThroughObjectMixin,
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class PyDotNewRFCTestCase(PyTestVectorsMixin, ThroughModuleAPIMixin,
|
class PyDotNewRFCTestCase(ThroughModuleAPIMixin, PyAssertersMixin,
|
||||||
RFCWithOpenSSLHashFunctionTestCasesMixin,
|
WithOpenSSLHashFunctions, RFCTestCaseMixin,
|
||||||
unittest.TestCase):
|
unittest.TestCase):
|
||||||
"""Python implementation of HMAC using hmac.new().
|
"""Python implementation of HMAC using hmac.new().
|
||||||
|
|
||||||
|
@ -598,12 +594,13 @@ class PyDotNewRFCTestCase(PyTestVectorsMixin, ThroughModuleAPIMixin,
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class OpenSSLRFCTestCase(OpenSSLTestVectorsMixin,
|
class OpenSSLRFCTestCase(OpenSSLAssertersMixin,
|
||||||
RFCWithOpenSSLHashFunctionTestCasesMixin,
|
WithOpenSSLHashFunctions, RFCTestCaseMixin,
|
||||||
unittest.TestCase):
|
unittest.TestCase):
|
||||||
"""OpenSSL implementation of HMAC.
|
"""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
|
# TODO(picnixz): once we have a HACL* HMAC, we should also test the Python
|
||||||
|
@ -668,7 +665,7 @@ class DigestModTestCaseMixin(CreatorMixin, DigestMixin):
|
||||||
return cases
|
return cases
|
||||||
|
|
||||||
|
|
||||||
class ConstructorTestCaseMixin(CreatorMixin, DigestMixin, CheckerMixin):
|
class ConstructorTestCaseMixin(CreatorMixin, DigestMixin, ObjectCheckerMixin):
|
||||||
"""HMAC constructor tests based on HMAC-SHA-2/256."""
|
"""HMAC constructor tests based on HMAC-SHA-2/256."""
|
||||||
|
|
||||||
key = b"key"
|
key = b"key"
|
||||||
|
@ -847,7 +844,7 @@ class PySanityTestCase(ThroughObjectMixin, PyModuleMixin, SanityTestCaseMixin,
|
||||||
self.assertStartsWith(repr(h), "<hmac.HMAC object at")
|
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,
|
class OpenSSLSanityTestCase(ThroughOpenSSLAPIMixin, SanityTestCaseMixin,
|
||||||
unittest.TestCase):
|
unittest.TestCase):
|
||||||
|
|
||||||
|
@ -899,45 +896,50 @@ class PyUpdateTestCase(UpdateTestCaseMixin, unittest.TestCase):
|
||||||
return self.hmac.HMAC(key, msg, digestmod='sha256')
|
return self.hmac.HMAC(key, msg, digestmod='sha256')
|
||||||
|
|
||||||
|
|
||||||
@hashlib_helper.requires_hashlib()
|
@hashlib_helper.requires_openssl_hashdigest('sha256')
|
||||||
@hashlib_helper.requires_hashdigest('sha256', openssl=True)
|
|
||||||
class OpenSSLUpdateTestCase(UpdateTestCaseMixin, unittest.TestCase):
|
class OpenSSLUpdateTestCase(UpdateTestCaseMixin, unittest.TestCase):
|
||||||
|
|
||||||
def HMAC(self, key, msg=None):
|
def HMAC(self, key, msg=None):
|
||||||
return hmac.new(key, msg, digestmod='sha256')
|
return hmac.new(key, msg, digestmod='sha256')
|
||||||
|
|
||||||
|
|
||||||
@hashlib_helper.requires_hashdigest('sha256')
|
class CopyBaseTestCase:
|
||||||
class CopyTestCase(unittest.TestCase):
|
|
||||||
|
|
||||||
def test_attributes_old(self):
|
def test_attributes(self):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def test_realcopy(self):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
@hashlib_helper.requires_hashdigest('sha256')
|
||||||
|
class PythonCopyTestCase(CopyBaseTestCase, unittest.TestCase):
|
||||||
|
|
||||||
|
def test_attributes(self):
|
||||||
# Testing if attributes are of same type.
|
# Testing if attributes are of same type.
|
||||||
h1 = hmac.HMAC.__new__(hmac.HMAC)
|
h1 = hmac.HMAC.__new__(hmac.HMAC)
|
||||||
h1._init_old(b"key", b"msg", digestmod="sha256")
|
h1._init_old(b"key", b"msg", digestmod="sha256")
|
||||||
h2 = h1.copy()
|
|
||||||
self.assertEqual(type(h1._inner), type(h2._inner))
|
|
||||||
self.assertEqual(type(h1._outer), type(h2._outer))
|
|
||||||
|
|
||||||
def test_realcopy_old(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)
|
self.assertIsNone(h1._hmac)
|
||||||
|
self.assertIsNotNone(h1._inner)
|
||||||
|
self.assertIsNotNone(h1._outer)
|
||||||
|
|
||||||
h2 = h1.copy()
|
h2 = h1.copy()
|
||||||
self.assertIsNone(h2._hmac)
|
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(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")
|
||||||
|
h2 = h1.copy()
|
||||||
# Using id() in case somebody has overridden __eq__/__ne__.
|
# Using id() in case somebody has overridden __eq__/__ne__.
|
||||||
self.assertNotEqual(id(h1), id(h2))
|
self.assertNotEqual(id(h1), id(h2))
|
||||||
self.assertNotEqual(id(h1._inner), id(h2._inner))
|
self.assertNotEqual(id(h1._inner), id(h2._inner))
|
||||||
self.assertNotEqual(id(h1._outer), id(h2._outer))
|
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):
|
def test_equality(self):
|
||||||
# Testing if the copy has the same digests.
|
# Testing if the copy has the same digests.
|
||||||
h1 = hmac.HMAC(b"key", digestmod="sha256")
|
h1 = hmac.HMAC(b"key", digestmod="sha256")
|
||||||
|
@ -951,11 +953,47 @@ class CopyTestCase(unittest.TestCase):
|
||||||
h1 = hmac.new(b"key", digestmod="sha256")
|
h1 = hmac.new(b"key", digestmod="sha256")
|
||||||
h1.update(b"some random text")
|
h1.update(b"some random text")
|
||||||
h2 = h1.copy()
|
h2 = h1.copy()
|
||||||
|
# Using id() in case somebody has overridden __eq__/__ne__.
|
||||||
self.assertNotEqual(id(h1), id(h2))
|
self.assertNotEqual(id(h1), id(h2))
|
||||||
self.assertEqual(h1.digest(), h2.digest())
|
self.assertEqual(h1.digest(), h2.digest())
|
||||||
self.assertEqual(h1.hexdigest(), h2.hexdigest())
|
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:
|
class CompareDigestMixin:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -1087,5 +1125,44 @@ class OperatorCompareDigestTestCase(CompareDigestMixin, unittest.TestCase):
|
||||||
compare_digest = operator_compare_digest
|
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__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue