@@ -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 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+
338352class 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
612635class 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():
753776class 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+
807852class 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+
862921class 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):
900959class 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
906974class CopyBaseTestCase :
@@ -991,7 +1059,16 @@ def test_realcopy(self):
9911059class 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
9971074class CompareDigestMixin :
0 commit comments