@@ -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 ( )
5151class 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+
131141class CheckerMixin :
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 , TestVectorsMixin ):
349+ pass
350+
351+
338352class HashFunctionsTrait :
339353 """Trait class for 'hashfunc' in hmac_new() and hmac_digest()."""
340354
@@ -605,6 +619,13 @@ class OpenSSLRFCTestCase(OpenSSLTestVectorsMixin,
605619 """
606620
607621
622+ class BuiltinRFCTestCase (BuiltinAssertersMixin ,
623+ WithNamedHashFunctions , RFCTestCasesMixin ,
624+ unittest .TestCase ):
625+ """Built-in HACL* implementation of HMAC.
626+
627+ The underlying hash functions are also HACL*-based.
628+ """
608629
609630# TODO(picnixz): once we have a HACL* HMAC, we should also test the Python
610631# implementation of HMAC with a HACL*-based hash function. For now, we only
@@ -613,7 +634,7 @@ class OpenSSLRFCTestCase(OpenSSLTestVectorsMixin,
613634
614635
615636class DigestModTestCaseMixin (CreatorMixin , DigestMixin ):
616- """Tests for the 'digestmod' parameter."""
637+ """Tests for the 'digestmod' parameter for hmac_new() and hmac_digest() ."""
617638
618639 def assert_raises_missing_digestmod (self ):
619640 """A context manager catching errors when a digestmod is missing."""
@@ -756,11 +777,15 @@ def raiser():
756777class ExtensionConstructorTestCaseMixin (DigestModTestCaseMixin ,
757778 ConstructorTestCaseMixin ):
758779
759- # The underlying C class.
760- obj_type = None
780+ @property
781+ def obj_type (self ):
782+ """The underlying (non-instantiable) C class."""
783+ raise NotImplementedError
761784
762- # The exact exception class raised when a 'digestmod' parameter is invalid.
763- exc_type = None
785+ @property
786+ def exc_type (self ):
787+ """The exact exception class raised upon invalid 'digestmod' values."""
788+ raise NotImplementedError
764789
765790 def test_internal_types (self ):
766791 # internal C types are immutable and cannot be instantiated
@@ -807,6 +832,24 @@ def test_hmac_digest_digestmod_parameter(self):
807832 self .hmac_digest (b'key' , b'msg' , value )
808833
809834
835+ class BuiltinConstructorTestCase (ThroughBuiltinAPIMixin ,
836+ ExtensionConstructorTestCaseMixin ,
837+ unittest .TestCase ):
838+
839+ @property
840+ def obj_type (self ):
841+ return self .hmac .HMAC
842+
843+ @property
844+ def exc_type (self ):
845+ return self .hmac .UnknownHashError
846+
847+ def test_hmac_digest_digestmod_parameter (self ):
848+ for value in [object , 'unknown' , 1234 , None ]:
849+ with self .subTest (value = value ), self .assert_digestmod_error ():
850+ self .hmac_digest (b'key' , b'msg' , value )
851+
852+
810853class SanityTestCaseMixin (CreatorMixin ):
811854 """Sanity checks for HMAC objects and their object interface.
812855
@@ -862,6 +905,20 @@ def test_repr(self):
862905 self .assertStartsWith (repr (h ), f"<{ self .digestname } HMAC object @" )
863906
864907
908+ class BuiltinSanityTestCase (ThroughBuiltinAPIMixin , SanityTestCaseMixin ,
909+ unittest .TestCase ):
910+
911+ @classmethod
912+ def setUpClass (cls ):
913+ super ().setUpClass ()
914+ cls .hmac_class = cls .hmac .HMAC
915+ cls .digestname = 'sha256'
916+
917+ def test_repr (self ):
918+ h = self .hmac_new (b"my secret key" , digestmod = self .digestname )
919+ self .assertStartsWith (repr (h ), f"<{ self .digestname } HMAC object @" )
920+
921+
865922class UpdateTestCaseMixin :
866923 """Tests for the update() method (streaming HMAC)."""
867924
@@ -893,7 +950,7 @@ class PyUpdateTestCase(UpdateTestCaseMixin, unittest.TestCase):
893950 @classmethod
894951 def setUpClass (cls ):
895952 super ().setUpClass ()
896- cls .hmac = import_fresh_module ('hmac' , blocked = ['_hashlib' ])
953+ cls .hmac = import_fresh_module ('hmac' , blocked = ['_hashlib' , '_hmac' ])
897954
898955 def HMAC (self , key , msg = None ):
899956 return self .hmac .HMAC (key , msg , digestmod = 'sha256' )
@@ -904,7 +961,16 @@ def HMAC(self, key, msg=None):
904961class OpenSSLUpdateTestCase (UpdateTestCaseMixin , unittest .TestCase ):
905962
906963 def HMAC (self , key , msg = None ):
907- return hmac .new (key , msg , digestmod = 'sha256' )
964+ return _hashlib .hmac_new (key , msg , digestmod = 'sha256' )
965+
966+
967+ class BuiltinUpdateTestCase (BuiltinModuleMixin ,
968+ UpdateTestCaseMixin , unittest .TestCase ):
969+
970+ def HMAC (self , key , msg = None ):
971+ # Even if Python does not build '_sha2', the HACL* sources
972+ # are still built, making it possible to use SHA-2 hashes.
973+ return self .hmac .new (key , msg , digestmod = 'sha256' )
908974
909975
910976@hashlib_helper .requires_hashdigest ('sha256' )
0 commit comments