Skip to content
Merged
Changes from 1 commit
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
d80b1b1
add helper for _hashlib detection
picnixz Feb 13, 2025
73b7756
remove unused function `ignore_warning`
picnixz Feb 13, 2025
fe7a300
cleanup whitespaces
picnixz Feb 13, 2025
72f956f
clean imports and test guards
picnixz Feb 13, 2025
3e827a7
Add mixins for interacting with HMAC
picnixz Feb 13, 2025
7f4fcc8
add mixin for RFC test cases
picnixz Feb 13, 2025
e47dd8b
use RFC mixin classes
picnixz Feb 13, 2025
acda122
refactor `CompareDigestTestCase`
picnixz Feb 13, 2025
d2eb48a
refactor `CopyTestCase`
picnixz Feb 13, 2025
105f6ce
refactor `UpdateTestCase`
picnixz Feb 13, 2025
412f5c2
refactor `SanityTestCase`
picnixz Feb 13, 2025
c682945
refactor `ConstructorTestCase`
picnixz Feb 13, 2025
7e62a58
Merge branch 'main' into test/hmac/refactor-99108
picnixz Feb 15, 2025
c04233d
update comments
picnixz Feb 15, 2025
fb7e577
extend coverage
picnixz Feb 15, 2025
168d018
Merge branch 'main' into test/hmac/refactor-99108
picnixz Feb 24, 2025
4177afa
add regression test for HMAC `repr()`
picnixz Feb 25, 2025
644f577
change a bit the digestmod tests
picnixz Feb 25, 2025
e71f03d
Merge branch 'main' into test/hmac/refactor-99108
picnixz Mar 2, 2025
dbe3ce4
remove mixin that is not yet needed
picnixz Mar 3, 2025
ca0822a
add comments
picnixz Mar 3, 2025
a0b3569
semantics: 'digest' -> 'hexdigest'
picnixz Mar 3, 2025
321a7a9
use `__init_subclass__` instead of `setUpClass`
picnixz Mar 3, 2025
ca7851c
document and reorganize method order in `DigestModTestCaseMixin`
picnixz Mar 3, 2025
a347ee5
use separate factories for creating bad digestmod cases
picnixz Mar 3, 2025
85364b4
minor tweak on `CompareDigestMixin`
picnixz Mar 3, 2025
740fe81
do not allow 'hashfunc' or 'hashname' to be None for RFC tests
picnixz Mar 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
add comments
  • Loading branch information
picnixz committed Mar 3, 2025
commit ca0822af0354eaa37d1e8f7b78f6cd78c38362aa
67 changes: 63 additions & 4 deletions Lib/test/test_hmac.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ class CreatorMixin:
"""Mixin exposing a method creating a HMAC object."""

def hmac_new(self, key, msg=None, digestmod=None):
"""Create a new HMAC object."""
raise NotImplementedError

def bind_hmac_new(self, digestmod):
Expand All @@ -72,6 +73,7 @@ class DigestMixin:
"""Mixin exposing a method computing a HMAC digest."""

def hmac_digest(self, key, msg=None, digestmod=None):
"""Compute a HMAC digest."""
raise NotImplementedError

def bind_hmac_digest(self, digestmod):
Expand All @@ -80,22 +82,30 @@ def bind_hmac_digest(self, digestmod):


class ThroughObjectMixin(ModuleMixin, CreatorMixin, DigestMixin):
"""Mixin delegating to <module>.HMAC() and <module>.HMAC(...).digest()."""
"""Mixin delegating to <module>.HMAC() and <module>.HMAC(...).digest().

Both the C implementation and the Python implementation of HMAC should
expose a HMAC class with the same functionalities.
"""

def hmac_new(self, key, msg=None, digestmod=None):
"""Create a HMAC object via a module-level class constructor."""
return self.hmac.HMAC(key, msg, digestmod=digestmod)

def hmac_digest(self, key, msg=None, digestmod=None):
"""Call the digest() method on a HMAC object obtained by hmac_new()."""
return self.hmac_new(key, msg, digestmod).digest()


class ThroughModuleAPIMixin(ModuleMixin, CreatorMixin, DigestMixin):
"""Mixin delegating to <module>.new() and <module>.digest()."""

def hmac_new(self, key, msg=None, digestmod=None):
"""Create a HMAC object via a module-level function."""
return self.hmac.new(key, msg, digestmod=digestmod)

def hmac_digest(self, key, msg=None, digestmod=None):
"""One-shot HMAC digest computation."""
return self.hmac.digest(key, msg, digest=digestmod)


Expand All @@ -114,15 +124,18 @@ class CheckerMixin:
"""Mixin for checking HMAC objects (pure Python, OpenSSL or built-in)."""

def check_object(self, h, digest, hashname, digest_size, block_size):
"""Check a HMAC object 'h' against the given values."""
self.check_internals(h, hashname, digest_size, block_size)
self.check_hexdigest(h, digest, digest_size)

def check_internals(self, h, hashname, digest_size, block_size):
"""Check the constant attributes of a HMAC object."""
self.assertEqual(h.name, f"hmac-{hashname}")
self.assertEqual(h.digest_size, digest_size)
self.assertEqual(h.block_size, block_size)

def check_hexdigest(self, h, digest, digest_size):
"""Check the HMAC digest of 'h' and its size."""
self.assertEqual(len(h.digest()), digest_size)
self.assertEqual(h.hexdigest().upper(), digest.upper())

Expand All @@ -131,7 +144,14 @@ class TestVectorsMixin(CreatorMixin, DigestMixin, CheckerMixin):
"""Mixin class for all test vectors test cases."""

def hmac_new_by_name(self, key, msg=None, hashname=None):
"""Alternative implementation of hmac_new()."""
"""Alternative implementation of hmac_new().

This is typically useful when one needs to test against an HMAC
implementation which only recognizes underlying hash functions
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".
"""
self.assertIsInstance(hashname, str | None)
return self.hmac_new(key, msg, digestmod=hashname)

Expand All @@ -143,6 +163,14 @@ def hmac_digest_by_name(self, key, msg=None, hashname=None):
def assert_hmac(
self, key, msg, digest, hashfunc, hashname, digest_size, block_size
):
"""Check that HMAC(key, msg) == digest.

The 'hashfunc' and 'hashname' are used as 'digestmod' values,
thereby allowing to test the underlying dispatching mechanism.

At most one of 'hashfunc' or 'hashname' value can be None, in which
case it is ignored.
"""
digestmods = list(filter(None, [hashfunc, hashname]))
self.assertNotEqual(digestmods, [],
"at least one implementation must be tested")
Expand Down Expand Up @@ -170,6 +198,10 @@ def assert_hmac(
def assert_hmac_new(
self, key, msg, digest, digestmod, hashname, digest_size, block_size
):
"""Check that HMAC(key, msg) == digest.

This test uses the `hmac_new()` method to create HMAC objects.
"""
self._check_hmac_new(
key, msg, digest, hashname, digest_size, block_size,
hmac_new_func=self.hmac_new,
Expand All @@ -179,6 +211,10 @@ def assert_hmac_new(
def assert_hmac_new_by_name(
self, key, msg, digest, hashname, digest_size, block_size
):
"""Check that HMAC(key, msg) == digest.

This test uses the `hmac_new_by_name()` method to create HMAC objects.
"""
self._check_hmac_new(
key, msg, digest, hashname, digest_size, block_size,
hmac_new_func=self.hmac_new_by_name,
Expand All @@ -189,6 +225,12 @@ def _check_hmac_new(
self, key, msg, digest, hashname, digest_size, block_size,
hmac_new_func, hmac_new_kwds,
):
"""Check that HMAC(key, msg) == digest.

This also tests that using an empty/None initial message and
then calling `h.update(msg)` produces the same result, namely
that HMAC(key, msg) is equivalent to HMAC(key).update(msg).
"""
h = hmac_new_func(key, msg, **hmac_new_kwds)
self.check_object(h, digest, hashname, digest_size, block_size)

Expand All @@ -207,13 +249,15 @@ def hmac_new_feed(*args):
def assert_hmac_digest(
self, key, msg, digest, 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(digest))

def assert_hmac_digest_by_new(
self, key, msg, digest, 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.assertEqual(len(d), digest_size)
Expand Down Expand Up @@ -676,7 +720,11 @@ def raiser():

class ExtensionConstructorTestCaseMixin(DigestModTestCaseMixin,
ConstructorTestCaseMixin):

# The underlying C class.
obj_type = None

# The exact exception class raised when a 'digestmod' parameter is invalid.
exc_type = None

def test_internal_types(self):
Expand Down Expand Up @@ -726,9 +774,16 @@ def test_hmac_digest_digestmod_parameter(self):


class SanityTestCaseMixin(CreatorMixin):
"""Sanity checks for HMAC objects and their object interface.

The tests here use a common digestname and do not check all supported
hash functions.
"""

hmac_class = None
digestname = None
# The underlying HMAC class to test. May be in C or in Python.
hmac_class: type
# The underlying hash function name (should be accepted by the HMAC class).
digestname: str

def test_methods(self):
h = self.hmac_new(b"my secret key", digestmod=self.digestname)
Expand All @@ -739,6 +794,7 @@ def test_methods(self):
self.assertIsInstance(h.copy(), self.hmac_class)

def test_repr(self):
# HMAC object representation may differ across implementations
raise NotImplementedError


Expand Down Expand Up @@ -773,8 +829,10 @@ def test_repr(self):


class UpdateTestCaseMixin:
"""Tests for the update() method (streaming HMAC)."""

def HMAC(self, key, msg=None):
"""Create a HMAC object."""
raise NotImplementedError

def test_update(self):
Expand Down Expand Up @@ -867,6 +925,7 @@ def test_equality_new(self):
class CompareDigestMixin:

def compare_digest(self, a, b):
"""Implementation of 'a == b' to test."""
raise NotImplementedError

def assert_digest_equal(self, a, b):
Expand Down