bpo-40645: use C implementation of HMAC (GH-24920)



- [x] fix tests
- [ ] add test scenarios for old/new code.

Signed-off-by: Christian Heimes <christian@python.org>
diff --git a/Lib/test/test_hmac.py b/Lib/test/test_hmac.py
index 6daf22c..adf52ad 100644
--- a/Lib/test/test_hmac.py
+++ b/Lib/test/test_hmac.py
@@ -11,14 +11,21 @@
 from _operator import _compare_digest as operator_compare_digest
 
 try:
+    import _hashlib as _hashopenssl
     from _hashlib import HMAC as C_HMAC
     from _hashlib import hmac_new as c_hmac_new
     from _hashlib import compare_digest as openssl_compare_digest
 except ImportError:
+    _hashopenssl = None
     C_HMAC = None
     c_hmac_new = None
     openssl_compare_digest = None
 
+try:
+    import _sha256 as sha256_module
+except ImportError:
+    sha256_module = None
+
 
 def ignore_warning(func):
     @functools.wraps(func)
@@ -32,22 +39,27 @@ def wrapper(*args, **kwargs):
 
 class TestVectorsTestCase(unittest.TestCase):
 
-    def asssert_hmac(
-        self, key, data, digest, hashfunc, hashname, digest_size, block_size
+    def assert_hmac_internals(
+            self, h, digest, hashname, digest_size, block_size
     ):
-        h = hmac.HMAC(key, data, digestmod=hashfunc)
         self.assertEqual(h.hexdigest().upper(), digest.upper())
         self.assertEqual(h.digest(), binascii.unhexlify(digest))
         self.assertEqual(h.name, f"hmac-{hashname}")
         self.assertEqual(h.digest_size, digest_size)
         self.assertEqual(h.block_size, block_size)
 
+    def assert_hmac(
+        self, key, data, digest, hashfunc, hashname, digest_size, block_size
+    ):
+        h = hmac.HMAC(key, data, digestmod=hashfunc)
+        self.assert_hmac_internals(
+            h, digest, hashname, digest_size, block_size
+        )
+
         h = hmac.HMAC(key, data, digestmod=hashname)
-        self.assertEqual(h.hexdigest().upper(), digest.upper())
-        self.assertEqual(h.digest(), binascii.unhexlify(digest))
-        self.assertEqual(h.name, f"hmac-{hashname}")
-        self.assertEqual(h.digest_size, digest_size)
-        self.assertEqual(h.block_size, block_size)
+        self.assert_hmac_internals(
+            h, digest, hashname, digest_size, block_size
+        )
 
         h = hmac.HMAC(key, digestmod=hashname)
         h2 = h.copy()
@@ -56,11 +68,9 @@ def asssert_hmac(
         self.assertEqual(h.hexdigest().upper(), digest.upper())
 
         h = hmac.new(key, data, digestmod=hashname)
-        self.assertEqual(h.hexdigest().upper(), digest.upper())
-        self.assertEqual(h.digest(), binascii.unhexlify(digest))
-        self.assertEqual(h.name, f"hmac-{hashname}")
-        self.assertEqual(h.digest_size, digest_size)
-        self.assertEqual(h.block_size, block_size)
+        self.assert_hmac_internals(
+            h, digest, hashname, digest_size, block_size
+        )
 
         h = hmac.new(key, None, digestmod=hashname)
         h.update(data)
@@ -81,23 +91,18 @@ def asssert_hmac(
             hmac.digest(key, data, digest=hashfunc),
             binascii.unhexlify(digest)
         )
-        with unittest.mock.patch('hmac._openssl_md_meths', {}):
-            self.assertEqual(
-                hmac.digest(key, data, digest=hashname),
-                binascii.unhexlify(digest)
-            )
-            self.assertEqual(
-                hmac.digest(key, data, digest=hashfunc),
-                binascii.unhexlify(digest)
-            )
+
+        h = hmac.HMAC.__new__(hmac.HMAC)
+        h._init_old(key, data, digestmod=hashname)
+        self.assert_hmac_internals(
+            h, digest, hashname, digest_size, block_size
+        )
 
         if c_hmac_new is not None:
             h = c_hmac_new(key, data, digestmod=hashname)
-            self.assertEqual(h.hexdigest().upper(), digest.upper())
-            self.assertEqual(h.digest(), binascii.unhexlify(digest))
-            self.assertEqual(h.name, f"hmac-{hashname}")
-            self.assertEqual(h.digest_size, digest_size)
-            self.assertEqual(h.block_size, block_size)
+            self.assert_hmac_internals(
+                h, digest, hashname, digest_size, block_size
+            )
 
             h = c_hmac_new(key, digestmod=hashname)
             h2 = h.copy()
@@ -105,12 +110,24 @@ def asssert_hmac(
             h.update(data)
             self.assertEqual(h.hexdigest().upper(), digest.upper())
 
+            func = getattr(_hashopenssl, f"openssl_{hashname}")
+            h = c_hmac_new(key, data, digestmod=func)
+            self.assert_hmac_internals(
+                h, digest, hashname, digest_size, block_size
+            )
+
+            h = hmac.HMAC.__new__(hmac.HMAC)
+            h._init_hmac(key, data, digestmod=hashname)
+            self.assert_hmac_internals(
+                h, digest, hashname, digest_size, block_size
+            )
+
     @hashlib_helper.requires_hashdigest('md5', openssl=True)
     def test_md5_vectors(self):
         # Test the HMAC module against test vectors from the RFC.
 
         def md5test(key, data, digest):
-            self.asssert_hmac(
+            self.assert_hmac(
                 key, data, digest,
                 hashfunc=hashlib.md5,
                 hashname="md5",
@@ -150,7 +167,7 @@ def md5test(key, data, digest):
     @hashlib_helper.requires_hashdigest('sha1', openssl=True)
     def test_sha_vectors(self):
         def shatest(key, data, digest):
-            self.asssert_hmac(
+            self.assert_hmac(
                 key, data, digest,
                 hashfunc=hashlib.sha1,
                 hashname="sha1",
@@ -191,7 +208,7 @@ def _rfc4231_test_cases(self, hashfunc, hash_name, digest_size, block_size):
         def hmactest(key, data, hexdigests):
             digest = hexdigests[hashfunc]
 
-            self.asssert_hmac(
+            self.assert_hmac(
                 key, data, digest,
                 hashfunc=hashfunc,
                 hashname=hash_name,
@@ -427,6 +444,15 @@ def test_internal_types(self):
         ):
             C_HMAC()
 
+    @unittest.skipUnless(sha256_module is not None, 'need _sha256')
+    def test_with_sha256_module(self):
+        h = hmac.HMAC(b"key", b"hash this!", digestmod=sha256_module.sha256)
+        self.assertEqual(h.hexdigest(), self.expected)
+        self.assertEqual(h.name, "hmac-sha256")
+
+        digest = hmac.digest(b"key", b"hash this!", sha256_module.sha256)
+        self.assertEqual(digest, binascii.unhexlify(self.expected))
+
 
 class SanityTestCase(unittest.TestCase):
 
@@ -447,21 +473,21 @@ def test_exercise_all_methods(self):
 class CopyTestCase(unittest.TestCase):
 
     @hashlib_helper.requires_hashdigest('sha256')
-    def test_attributes(self):
+    def test_attributes_old(self):
         # Testing if attributes are of same type.
-        h1 = hmac.HMAC(b"key", digestmod="sha256")
+        h1 = hmac.HMAC.__new__(hmac.HMAC)
+        h1._init_old(b"key", b"msg", digestmod="sha256")
         h2 = h1.copy()
-        self.assertTrue(h1._digest_cons == h2._digest_cons,
-            "digest constructors don't match.")
         self.assertEqual(type(h1._inner), type(h2._inner),
             "Types of inner don't match.")
         self.assertEqual(type(h1._outer), type(h2._outer),
             "Types of outer don't match.")
 
     @hashlib_helper.requires_hashdigest('sha256')
-    def test_realcopy(self):
+    def test_realcopy_old(self):
         # Testing if the copy method created a real copy.
-        h1 = hmac.HMAC(b"key", digestmod="sha256")
+        h1 = hmac.HMAC.__new__(hmac.HMAC)
+        h1._init_old(b"key", b"msg", digestmod="sha256")
         h2 = h1.copy()
         # Using id() in case somebody has overridden __eq__/__ne__.
         self.assertTrue(id(h1) != id(h2), "No real copy of the HMAC instance.")
@@ -469,17 +495,15 @@ def test_realcopy(self):
             "No real copy of the attribute 'inner'.")
         self.assertTrue(id(h1._outer) != id(h2._outer),
             "No real copy of the attribute 'outer'.")
-        self.assertEqual(h1._inner, h1.inner)
-        self.assertEqual(h1._outer, h1.outer)
-        self.assertEqual(h1._digest_cons, h1.digest_cons)
+        self.assertIs(h1._hmac, None)
 
+    @unittest.skipIf(_hashopenssl is None, "test requires _hashopenssl")
     @hashlib_helper.requires_hashdigest('sha256')
-    def test_properties(self):
-        # deprecated properties
-        h1 = hmac.HMAC(b"key", digestmod="sha256")
-        self.assertEqual(h1._inner, h1.inner)
-        self.assertEqual(h1._outer, h1.outer)
-        self.assertEqual(h1._digest_cons, h1.digest_cons)
+    def test_realcopy_hmac(self):
+        h1 = hmac.HMAC.__new__(hmac.HMAC)
+        h1._init_hmac(b"key", b"msg", digestmod="sha256")
+        h2 = h1.copy()
+        self.assertTrue(id(h1._hmac) != id(h2._hmac))
 
     @hashlib_helper.requires_hashdigest('sha256')
     def test_equality(self):