@@ -329,28 +329,50 @@ def hmac_digest_by_name(self, key, msg=None, hashname=None):
329329 return self .hmac_digest (key , msg , digestmod = openssl_func )
330330
331331
332- class RFCTestCasesMixin (TestVectorsMixin ):
333- """Test HMAC implementations against test vectors from the RFC.
334-
335- Subclasses must override the 'md5' and other 'sha*' attributes
336- to test the implementations. Their value can be a string, a callable,
337- or a PEP-257 module.
338- """
332+ class HashFunctionsTrait :
333+ """Trait class for 'hashfunc' in hmac_new() and hmac_digest()."""
339334
340335 ALGORITHMS = [
341336 'md5' , 'sha1' ,
342337 'sha224' , 'sha256' , 'sha384' , 'sha512' ,
343338 ]
344339
345- # Those will be automatically set to non-None on subclasses
346- # as they are set by __init_subclass()__.
347- md5 = sha1 = sha224 = sha256 = sha384 = sha512 = None
340+ # By default, a missing algorithm skips the test that uses it.
341+ md5 = sha1 = sha224 = sha256 = sha384 = sha512 = property (
342+ lambda self : self .skipTest ("missing hash function" )
343+ )
344+
345+
346+ class WithOpenSSLHashFunctions (HashFunctionsTrait ):
347+ """Test a HMAC implementation with an OpenSSL-based callable 'hashfunc'."""
348+
349+ @classmethod
350+ def setUpClass (cls ):
351+ super ().setUpClass ()
352+
353+ for name in cls .ALGORITHMS :
354+ @property
355+ @hashlib_helper .requires_hashlib ()
356+ @hashlib_helper .requires_hashdigest (name , openssl = True )
357+ def func (self , * , __name = name ): # __name needed to bind 'name'
358+ return getattr (_hashlib , f'openssl_{ __name } ' )
359+ setattr (cls , name , func )
360+
361+
362+ class WithNamedHashFunctions (HashFunctionsTrait ):
363+ """Test a HMAC implementation with a named 'hashfunc'."""
364+
365+ @classmethod
366+ def setUpClass (cls ):
367+ super ().setUpClass ()
348368
349- def __init_subclass__ (cls , * args , ** kwargs ):
350- super ().__init_subclass__ (* args , ** kwargs )
351369 for name in cls .ALGORITHMS :
352370 setattr (cls , name , name )
353371
372+
373+ class RFCTestCasesMixin (HashFunctionsTrait ):
374+ """Test HMAC implementations against test vectors from the RFC."""
375+
354376 def test_md5 (self ):
355377 def md5test (key , msg , hexdigest ):
356378 self .assert_hmac (key , msg , hexdigest , self .md5 , "md5" , 16 , 64 )
@@ -550,31 +572,17 @@ def hmactest(key, data, hexdigests):
550572 })
551573
552574
553- class RFCWithOpenSSLHashFunctionTestCasesMixin (RFCTestCasesMixin ):
554-
555- def __init_subclass__ (cls , * args , ** kwargs ):
556- super ().__init_subclass__ (* args , ** kwargs )
557-
558- for name in cls .ALGORITHMS :
559- @property
560- @hashlib_helper .requires_hashlib ()
561- @hashlib_helper .requires_hashdigest (name , openssl = True )
562- def func (self , * , __name = name ): # __name needed to bind 'name'
563- return getattr (_hashlib , f'openssl_{ __name } ' )
564- setattr (cls , name , func )
565-
566-
567- class PyRFCTestCase (PyTestVectorsMixin , ThroughObjectMixin ,
568- RFCWithOpenSSLHashFunctionTestCasesMixin ,
575+ class PyRFCTestCase (ThroughObjectMixin , PyTestVectorsMixin ,
576+ WithOpenSSLHashFunctions , RFCTestCasesMixin ,
569577 unittest .TestCase ):
570578 """Python implementation of HMAC using hmac.HMAC().
571579
572580 The underlying hash functions are OpenSSL-based.
573581 """
574582
575583
576- class PyDotNewRFCTestCase (PyTestVectorsMixin , ThroughModuleAPIMixin ,
577- RFCWithOpenSSLHashFunctionTestCasesMixin ,
584+ class PyDotNewRFCTestCase (ThroughModuleAPIMixin , PyTestVectorsMixin ,
585+ WithOpenSSLHashFunctions , RFCTestCasesMixin ,
578586 unittest .TestCase ):
579587 """Python implementation of HMAC using hmac.new().
580588
@@ -583,11 +591,13 @@ class PyDotNewRFCTestCase(PyTestVectorsMixin, ThroughModuleAPIMixin,
583591
584592
585593class OpenSSLRFCTestCase (OpenSSLTestVectorsMixin ,
586- RFCWithOpenSSLHashFunctionTestCasesMixin ,
594+ WithOpenSSLHashFunctions , RFCTestCasesMixin ,
587595 unittest .TestCase ):
588596 """OpenSSL implementation of HMAC.
589597
590- The underlying hash functions are also OpenSSL-based."""
598+ The underlying hash functions are also OpenSSL-based.
599+ """
600+
591601
592602
593603# TODO(picnixz): once we have a HACL* HMAC, we should also test the Python
0 commit comments