Skip to content

Commit 2b544e5

Browse files
committed
add various tests for the HACL* HMAC implementation
- create `ThroughBuiltinAPIMixin` similar to `ThroughOpenSSLAPIMixin` - add tests for RFC test vectors - add tests for `digestmod` parameter - add sanity check tests - add hmac.update() tests - add hmac.copy() tests
1 parent f2817ae commit 2b544e5

File tree

1 file changed

+86
-9
lines changed

1 file changed

+86
-9
lines changed

Lib/test/test_hmac.py

Lines changed: 86 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def setUpClass(cls):
4747
cls.hmac = import_fresh_module('hmac', blocked=['_hashlib', '_hmac'])
4848

4949

50-
@unittest.skip("no builtin implementation for HMAC for now")
50+
@hashlib_helper.requires_builtin_hmac()
5151
class BuiltinModuleMixin(ModuleMixin):
5252
"""Built-in HACL* implementation of HMAC."""
5353

@@ -128,6 +128,16 @@ def hmac_digest(self, key, msg=None, digestmod=None):
128128
return _hashlib.hmac_digest(key, msg, digest=digestmod)
129129

130130

131+
class ThroughBuiltinAPIMixin(BuiltinModuleMixin, CreatorMixin, DigestMixin):
132+
"""Mixin delegating to _hmac.new() and _hmac.compute_digest()."""
133+
134+
def hmac_new(self, key, msg=None, digestmod=None):
135+
return self.hmac.new(key, msg, digestmod=digestmod)
136+
137+
def hmac_digest(self, key, msg=None, digestmod=None):
138+
return self.hmac.compute_digest(key, msg, digest=digestmod)
139+
140+
131141
class ObjectCheckerMixin:
132142
"""Mixin for checking HMAC objects (pure Python, OpenSSL or built-in)."""
133143

@@ -335,6 +345,10 @@ def hmac_digest_by_name(self, key, msg=None, *, hashname):
335345
return self.hmac_digest(key, msg, digestmod=openssl_func)
336346

337347

348+
class BuiltinAssertersMixin(ThroughBuiltinAPIMixin, AssertersMixin):
349+
pass
350+
351+
338352
class HashFunctionsTrait:
339353
"""Trait class for 'hashfunc' in hmac_new() and hmac_digest()."""
340354

@@ -603,14 +617,23 @@ class OpenSSLRFCTestCase(OpenSSLAssertersMixin,
603617
"""
604618

605619

620+
class BuiltinRFCTestCase(BuiltinAssertersMixin,
621+
WithNamedHashFunctions, RFCTestCaseMixin,
622+
unittest.TestCase):
623+
"""Built-in HACL* implementation of HMAC.
624+
625+
The underlying hash functions are also HACL*-based.
626+
"""
627+
628+
606629
# TODO(picnixz): once we have a HACL* HMAC, we should also test the Python
607630
# implementation of HMAC with a HACL*-based hash function. For now, we only
608631
# test it partially via the '_sha2' module, but for completeness we could
609632
# also test the RFC test vectors against all possible implementations.
610633

611634

612635
class DigestModTestCaseMixin(CreatorMixin, DigestMixin):
613-
"""Tests for the 'digestmod' parameter."""
636+
"""Tests for the 'digestmod' parameter for hmac_new() and hmac_digest()."""
614637

615638
def assert_raises_missing_digestmod(self):
616639
"""A context manager catching errors when a digestmod is missing."""
@@ -753,11 +776,15 @@ def raiser():
753776
class ExtensionConstructorTestCaseMixin(DigestModTestCaseMixin,
754777
ConstructorTestCaseMixin):
755778

756-
# The underlying C class.
757-
obj_type = None
779+
@property
780+
def obj_type(self):
781+
"""The underlying (non-instantiable) C class."""
782+
raise NotImplementedError
758783

759-
# The exact exception class raised when a 'digestmod' parameter is invalid.
760-
exc_type = None
784+
@property
785+
def exc_type(self):
786+
"""The exact exception class raised upon invalid 'digestmod' values."""
787+
raise NotImplementedError
761788

762789
def test_internal_types(self):
763790
# internal C types are immutable and cannot be instantiated
@@ -804,6 +831,24 @@ def test_hmac_digest_digestmod_parameter(self):
804831
self.hmac_digest(b'key', b'msg', value)
805832

806833

834+
class BuiltinConstructorTestCase(ThroughBuiltinAPIMixin,
835+
ExtensionConstructorTestCaseMixin,
836+
unittest.TestCase):
837+
838+
@property
839+
def obj_type(self):
840+
return self.hmac.HMAC
841+
842+
@property
843+
def exc_type(self):
844+
return self.hmac.UnknownHashError
845+
846+
def test_hmac_digest_digestmod_parameter(self):
847+
for value in [object, 'unknown', 1234, None]:
848+
with self.subTest(value=value), self.assert_digestmod_error():
849+
self.hmac_digest(b'key', b'msg', value)
850+
851+
807852
class SanityTestCaseMixin(CreatorMixin):
808853
"""Sanity checks for HMAC objects and their object interface.
809854
@@ -859,6 +904,20 @@ def test_repr(self):
859904
self.assertStartsWith(repr(h), f"<{self.digestname} HMAC object @")
860905

861906

907+
class BuiltinSanityTestCase(ThroughBuiltinAPIMixin, SanityTestCaseMixin,
908+
unittest.TestCase):
909+
910+
@classmethod
911+
def setUpClass(cls):
912+
super().setUpClass()
913+
cls.hmac_class = cls.hmac.HMAC
914+
cls.digestname = 'sha256'
915+
916+
def test_repr(self):
917+
h = self.hmac_new(b"my secret key", digestmod=self.digestname)
918+
self.assertStartsWith(repr(h), f"<{self.digestname} HMAC object @")
919+
920+
862921
class UpdateTestCaseMixin:
863922
"""Tests for the update() method (streaming HMAC)."""
864923

@@ -890,7 +949,7 @@ class PyUpdateTestCase(UpdateTestCaseMixin, unittest.TestCase):
890949
@classmethod
891950
def setUpClass(cls):
892951
super().setUpClass()
893-
cls.hmac = import_fresh_module('hmac', blocked=['_hashlib'])
952+
cls.hmac = import_fresh_module('hmac', blocked=['_hashlib', '_hmac'])
894953

895954
def HMAC(self, key, msg=None):
896955
return self.hmac.HMAC(key, msg, digestmod='sha256')
@@ -900,7 +959,16 @@ def HMAC(self, key, msg=None):
900959
class OpenSSLUpdateTestCase(UpdateTestCaseMixin, unittest.TestCase):
901960

902961
def HMAC(self, key, msg=None):
903-
return hmac.new(key, msg, digestmod='sha256')
962+
return _hashlib.hmac_new(key, msg, digestmod='sha256')
963+
964+
965+
class BuiltinUpdateTestCase(BuiltinModuleMixin,
966+
UpdateTestCaseMixin, unittest.TestCase):
967+
968+
def HMAC(self, key, msg=None):
969+
# Even if Python does not build '_sha2', the HACL* sources
970+
# are still built, making it possible to use SHA-2 hashes.
971+
return self.hmac.new(key, msg, digestmod='sha256')
904972

905973

906974
class CopyBaseTestCase:
@@ -991,7 +1059,16 @@ def test_realcopy(self):
9911059
class OpenSSLCopyTestCase(ExtensionCopyTestCase, unittest.TestCase):
9921060

9931061
def init(self, h):
994-
h._init_hmac(b"key", b"msg", digestmod="sha256")
1062+
h._init_openssl_hmac(b"key", b"msg", digestmod="sha256")
1063+
1064+
1065+
@hashlib_helper.requires_builtin_hmac()
1066+
class BuiltinCopyTestCase(ExtensionCopyTestCase, unittest.TestCase):
1067+
1068+
def init(self, h):
1069+
# Even if Python does not build '_sha2', the HACL* sources
1070+
# are still built, making it possible to use SHA-2 hashes.
1071+
h._init_builtin_hmac(b"key", b"msg", digestmod="sha256")
9951072

9961073

9971074
class CompareDigestMixin:

0 commit comments

Comments
 (0)