[Python-checkins] bpo-40645: Implement HMAC in C (GH-20129)

Christian Heimes webhook-mailer at python.org
Sun May 17 07:49:14 EDT 2020


https://github.com/python/cpython/commit/54f2898fe7e4ca1f239e96284af3cc5b34d2ae02
commit: 54f2898fe7e4ca1f239e96284af3cc5b34d2ae02
branch: master
author: Christian Heimes <christian at python.org>
committer: GitHub <noreply at github.com>
date: 2020-05-17T13:49:10+02:00
summary:

bpo-40645: Implement HMAC in C (GH-20129)

The internal module ``_hashlib`` wraps and exposes OpenSSL's HMAC API. The
new code will be used in Python 3.10 after the internal implementation
details of the pure Python HMAC module are no longer part of the public API.

The code is based on a patch by Petr Viktorin for RHEL and Python 3.6.

Co-Authored-By: Petr Viktorin <encukou at gmail.com>

files:
A Misc/NEWS.d/next/Library/2020-05-16-17-05-02.bpo-40645.wYSkjT.rst
M Lib/test/test_hmac.py
M Modules/_hashopenssl.c
M Modules/clinic/_hashopenssl.c.h

diff --git a/Lib/test/test_hmac.py b/Lib/test/test_hmac.py
index 1f3ec4cb9172d..7a52e39c5d471 100644
--- a/Lib/test/test_hmac.py
+++ b/Lib/test/test_hmac.py
@@ -8,6 +8,13 @@
 
 from test.support import hashlib_helper
 
+try:
+    from _hashlib import HMAC as C_HMAC
+    from _hashlib import hmac_new as c_hmac_new
+except ImportError:
+    C_HMAC = None
+    c_hmac_new = None
+
 
 def ignore_warning(func):
     @functools.wraps(func)
@@ -21,34 +28,91 @@ def wrapper(*args, **kwargs):
 
 class TestVectorsTestCase(unittest.TestCase):
 
-    @hashlib_helper.requires_hashdigest('md5', openssl=True)
-    def test_md5_vectors(self):
-        # Test the HMAC module against test vectors from the RFC.
+    def asssert_hmac(
+        self, key, data, digest, hashfunc, 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)
+
+        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)
+
+        h = hmac.HMAC(key, digestmod=hashname)
+        h2 = h.copy()
+        h2.update(b"test update")
+        h.update(data)
+        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)
+
+        h = hmac.new(key, None, digestmod=hashname)
+        h.update(data)
+        self.assertEqual(h.hexdigest().upper(), digest.upper())
+
+        h = hmac.new(key, digestmod=hashname)
+        h.update(data)
+        self.assertEqual(h.hexdigest().upper(), digest.upper())
+
+        h = hmac.new(key, data, digestmod=hashfunc)
+        self.assertEqual(h.hexdigest().upper(), digest.upper())
+
+        self.assertEqual(
+            hmac.digest(key, data, digest=hashname),
+            binascii.unhexlify(digest)
+        )
+        self.assertEqual(
+            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)
+            )
 
-        def md5test(key, data, digest):
-            h = hmac.HMAC(key, data, digestmod=hashlib.md5)
+        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, "hmac-md5")
-            self.assertEqual(h.digest_size, 16)
-            self.assertEqual(h.block_size, 64)
+            self.assertEqual(h.name, f"hmac-{hashname}")
+            self.assertEqual(h.digest_size, digest_size)
+            self.assertEqual(h.block_size, block_size)
 
-            h = hmac.HMAC(key, data, digestmod='md5')
+            h = c_hmac_new(key, digestmod=hashname)
+            h2 = h.copy()
+            h2.update(b"test update")
+            h.update(data)
             self.assertEqual(h.hexdigest().upper(), digest.upper())
-            self.assertEqual(h.digest(), binascii.unhexlify(digest))
-            self.assertEqual(h.name, "hmac-md5")
-            self.assertEqual(h.digest_size, 16)
-            self.assertEqual(h.block_size, 64)
 
-            self.assertEqual(
-                hmac.digest(key, data, digest='md5'),
-                binascii.unhexlify(digest)
+    @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(
+                key, data, digest,
+                hashfunc=hashlib.md5,
+                hashname="md5",
+                digest_size=16,
+                block_size=64
             )
-            with unittest.mock.patch('hmac._openssl_md_meths', {}):
-                self.assertEqual(
-                    hmac.digest(key, data, digest='md5'),
-                    binascii.unhexlify(digest)
-                )
 
         md5test(b"\x0b" * 16,
                 b"Hi There",
@@ -82,26 +146,14 @@ def md5test(key, data, digest):
     @hashlib_helper.requires_hashdigest('sha1', openssl=True)
     def test_sha_vectors(self):
         def shatest(key, data, digest):
-            h = hmac.HMAC(key, data, digestmod=hashlib.sha1)
-            self.assertEqual(h.hexdigest().upper(), digest.upper())
-            self.assertEqual(h.digest(), binascii.unhexlify(digest))
-            self.assertEqual(h.name, "hmac-sha1")
-            self.assertEqual(h.digest_size, 20)
-            self.assertEqual(h.block_size, 64)
-
-            h = hmac.HMAC(key, data, digestmod='sha1')
-            self.assertEqual(h.hexdigest().upper(), digest.upper())
-            self.assertEqual(h.digest(), binascii.unhexlify(digest))
-            self.assertEqual(h.name, "hmac-sha1")
-            self.assertEqual(h.digest_size, 20)
-            self.assertEqual(h.block_size, 64)
-
-            self.assertEqual(
-                hmac.digest(key, data, digest='sha1'),
-                binascii.unhexlify(digest)
+            self.asssert_hmac(
+                key, data, digest,
+                hashfunc=hashlib.sha1,
+                hashname="sha1",
+                digest_size=20,
+                block_size=64
             )
 
-
         shatest(b"\x0b" * 20,
                 b"Hi There",
                 "b617318655057264e28bc0b6fb378c8ef146be00")
@@ -133,37 +185,15 @@ def shatest(key, data, digest):
 
     def _rfc4231_test_cases(self, hashfunc, hash_name, digest_size, block_size):
         def hmactest(key, data, hexdigests):
-            hmac_name = "hmac-" + hash_name
-            h = hmac.HMAC(key, data, digestmod=hashfunc)
-            self.assertEqual(h.hexdigest().lower(), hexdigests[hashfunc])
-            self.assertEqual(h.name, hmac_name)
-            self.assertEqual(h.digest_size, digest_size)
-            self.assertEqual(h.block_size, block_size)
-
-            h = hmac.HMAC(key, data, digestmod=hash_name)
-            self.assertEqual(h.hexdigest().lower(), hexdigests[hashfunc])
-            self.assertEqual(h.name, hmac_name)
-            self.assertEqual(h.digest_size, digest_size)
-            self.assertEqual(h.block_size, block_size)
-
-            self.assertEqual(
-                hmac.digest(key, data, digest=hashfunc),
-                binascii.unhexlify(hexdigests[hashfunc])
+            digest = hexdigests[hashfunc]
+
+            self.asssert_hmac(
+                key, data, digest,
+                hashfunc=hashfunc,
+                hashname=hash_name,
+                digest_size=digest_size,
+                block_size=block_size
             )
-            self.assertEqual(
-                hmac.digest(key, data, digest=hash_name),
-                binascii.unhexlify(hexdigests[hashfunc])
-            )
-
-            with unittest.mock.patch('hmac._openssl_md_meths', {}):
-                self.assertEqual(
-                    hmac.digest(key, data, digest=hashfunc),
-                    binascii.unhexlify(hexdigests[hashfunc])
-                )
-                self.assertEqual(
-                    hmac.digest(key, data, digest=hash_name),
-                    binascii.unhexlify(hexdigests[hashfunc])
-                )
 
         # 4.2.  Test Case 1
         hmactest(key = b'\x0b'*20,
@@ -385,6 +415,14 @@ def test_withmodule(self):
         except Exception:
             self.fail("Constructor call with hashlib.sha256 raised exception.")
 
+    @unittest.skipUnless(C_HMAC is not None, 'need _hashlib')
+    def test_internal_types(self):
+        # internal types like _hashlib.C_HMAC are not constructable
+        with self.assertRaisesRegex(
+            TypeError, "cannot create 'HMAC' instance"
+        ):
+            C_HMAC()
+
 
 class SanityTestCase(unittest.TestCase):
 
@@ -395,9 +433,9 @@ def test_exercise_all_methods(self):
         try:
             h = hmac.HMAC(b"my secret key", digestmod="sha256")
             h.update(b"compute the hash of this text!")
-            dig = h.digest()
-            dig = h.hexdigest()
-            h2 = h.copy()
+            h.digest()
+            h.hexdigest()
+            h.copy()
         except Exception:
             self.fail("Exception raised during normal usage of HMAC class.")
 
@@ -450,6 +488,21 @@ def test_equality(self):
         self.assertEqual(h1.hexdigest(), h2.hexdigest(),
             "Hexdigest of copy doesn't match original hexdigest.")
 
+    @hashlib_helper.requires_hashdigest('sha256')
+    def test_equality_new(self):
+        # Testing if the copy has the same digests with hmac.new().
+        h1 = hmac.new(b"key", digestmod="sha256")
+        h1.update(b"some random text")
+        h2 = h1.copy()
+        self.assertTrue(
+            id(h1) != id(h2), "No real copy of the HMAC instance."
+        )
+        self.assertEqual(h1.digest(), h2.digest(),
+            "Digest of copy doesn't match original digest.")
+        self.assertEqual(h1.hexdigest(), h2.hexdigest(),
+            "Hexdigest of copy doesn't match original hexdigest.")
+
+
 class CompareDigestTestCase(unittest.TestCase):
 
     def test_compare_digest(self):
diff --git a/Misc/NEWS.d/next/Library/2020-05-16-17-05-02.bpo-40645.wYSkjT.rst b/Misc/NEWS.d/next/Library/2020-05-16-17-05-02.bpo-40645.wYSkjT.rst
new file mode 100644
index 0000000000000..bb7eacdc5aa78
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2020-05-16-17-05-02.bpo-40645.wYSkjT.rst
@@ -0,0 +1 @@
+The internal module ``_hashlib`` wraps and exposes OpenSSL's HMAC API. The new code will be used in Python 3.10 after the internal implementation details of the pure Python HMAC module are no longer part of the public API.
diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c
index 936b515addbc1..292e92a375268 100644
--- a/Modules/_hashopenssl.c
+++ b/Modules/_hashopenssl.c
@@ -35,6 +35,32 @@
 /* OpenSSL < 1.1.0 */
 #define EVP_MD_CTX_new EVP_MD_CTX_create
 #define EVP_MD_CTX_free EVP_MD_CTX_destroy
+
+HMAC_CTX *
+HMAC_CTX_new(void)
+{
+    HMAC_CTX *ctx = OPENSSL_malloc(sizeof(HMAC_CTX));
+    if (ctx != NULL) {
+        memset(ctx, 0, sizeof(HMAC_CTX));
+        HMAC_CTX_init(ctx);
+    }
+    return ctx;
+}
+
+void
+HMAC_CTX_free(HMAC_CTX *ctx)
+{
+    if (ctx != NULL) {
+        HMAC_CTX_cleanup(ctx);
+        OPENSSL_free(ctx);
+    }
+}
+
+const EVP_MD *
+HMAC_CTX_get_md(const HMAC_CTX *ctx)
+{
+    return ctx->md;
+}
 #endif
 
 #define MUNCH_SIZE INT_MAX
@@ -55,6 +81,7 @@ static PyModuleDef _hashlibmodule;
 
 typedef struct {
     PyTypeObject *EVPtype;
+    PyTypeObject *HMACtype;
 #ifdef PY_OPENSSL_HAS_SHAKE
     PyTypeObject *EVPXOFtype;
 #endif
@@ -77,14 +104,20 @@ typedef struct {
     PyThread_type_lock   lock;  /* OpenSSL context lock */
 } EVPobject;
 
+typedef struct {
+    PyObject_HEAD
+    HMAC_CTX *ctx;            /* OpenSSL hmac context */
+    PyThread_type_lock lock;  /* HMAC context lock */
+} HMACobject;
 
 #include "clinic/_hashopenssl.c.h"
 /*[clinic input]
 module _hashlib
 class _hashlib.HASH "EVPobject *" "((_hashlibstate *)PyModule_GetState(module))->EVPtype"
 class _hashlib.HASHXOF "EVPobject *" "((_hashlibstate *)PyModule_GetState(module))->EVPXOFtype"
+class _hashlib.HMAC "HMACobject *" "((_hashlibstate *)PyModule_GetState(module))->HMACtype"
 [clinic start generated code]*/
-/*[clinic end generated code: output=da39a3ee5e6b4b0d input=813acc7b2d8f322c]*/
+/*[clinic end generated code: output=da39a3ee5e6b4b0d input=7df1bcf6f75cb8ef]*/
 
 
 /* LCOV_EXCL_START */
@@ -1091,7 +1124,7 @@ pbkdf2_hmac_impl(PyObject *module, const char *hash_name,
     int retval;
     const EVP_MD *digest;
 
-    digest = EVP_get_digestbyname(hash_name);
+    digest = py_digest_by_name(hash_name);
     if (digest == NULL) {
         PyErr_SetString(PyExc_ValueError, "unsupported hash type");
         goto end;
@@ -1293,7 +1326,7 @@ _hashlib_scrypt_impl(PyObject *module, Py_buffer *password, Py_buffer *salt,
  */
 
 /*[clinic input]
-_hashlib.hmac_digest
+_hashlib.hmac_digest as _hashlib_hmac_singleshot
 
     key: Py_buffer
     msg: Py_buffer
@@ -1303,16 +1336,16 @@ Single-shot HMAC.
 [clinic start generated code]*/
 
 static PyObject *
-_hashlib_hmac_digest_impl(PyObject *module, Py_buffer *key, Py_buffer *msg,
-                          const char *digest)
-/*[clinic end generated code: output=75630e684cdd8762 input=562d2f4249511bd3]*/
+_hashlib_hmac_singleshot_impl(PyObject *module, Py_buffer *key,
+                              Py_buffer *msg, const char *digest)
+/*[clinic end generated code: output=15658ede5ab98185 input=019dffc571909a46]*/
 {
     unsigned char md[EVP_MAX_MD_SIZE] = {0};
     unsigned int md_len = 0;
     unsigned char *result;
     const EVP_MD *evp;
 
-    evp = EVP_get_digestbyname(digest);
+    evp = py_digest_by_name(digest);
     if (evp == NULL) {
         PyErr_SetString(PyExc_ValueError, "unsupported hash type");
         return NULL;
@@ -1344,6 +1377,354 @@ _hashlib_hmac_digest_impl(PyObject *module, Py_buffer *key, Py_buffer *msg,
     return PyBytes_FromStringAndSize((const char*)md, md_len);
 }
 
+/* OpenSSL-based HMAC implementation
+ */
+
+static int _hmac_update(HMACobject*, PyObject*);
+
+/*[clinic input]
+_hashlib.hmac_new
+
+    key: Py_buffer
+    msg as msg_obj: object(c_default="NULL") = b''
+    digestmod: str(c_default="NULL") = None
+
+Return a new hmac object.
+[clinic start generated code]*/
+
+static PyObject *
+_hashlib_hmac_new_impl(PyObject *module, Py_buffer *key, PyObject *msg_obj,
+                       const char *digestmod)
+/*[clinic end generated code: output=9a35673be0cbea1b input=a0878868eb190134]*/
+{
+    PyTypeObject *type = get_hashlib_state(module)->HMACtype;
+    const EVP_MD *digest;
+    HMAC_CTX *ctx = NULL;
+    HMACobject *self = NULL;
+    int r;
+
+    if ((digestmod == NULL) || !strlen(digestmod)) {
+        PyErr_SetString(
+            PyExc_TypeError, "Missing required parameter 'digestmod'.");
+        return NULL;
+    }
+
+    digest = py_digest_by_name(digestmod);
+    if (!digest) {
+        PyErr_SetString(PyExc_ValueError, "unknown hash function");
+        return NULL;
+    }
+
+    ctx = HMAC_CTX_new();
+    if (ctx == NULL) {
+        _setException(PyExc_ValueError);
+        goto error;
+    }
+
+    r = HMAC_Init_ex(
+        ctx,
+        (const char*)key->buf,
+        key->len,
+        digest,
+        NULL /*impl*/);
+    if (r == 0) {
+        _setException(PyExc_ValueError);
+        goto error;
+    }
+
+    self = (HMACobject *)PyObject_New(HMACobject, type);
+    if (self == NULL) {
+        goto error;
+    }
+
+    self->ctx = ctx;
+    self->lock = NULL;
+
+    if ((msg_obj != NULL) && (msg_obj != Py_None)) {
+        if (!_hmac_update(self, msg_obj))
+            goto error;
+    }
+
+    return (PyObject*)self;
+
+error:
+    if (ctx) HMAC_CTX_free(ctx);
+    if (self) PyObject_Del(self);
+    return NULL;
+}
+
+/* helper functions */
+static int
+locked_HMAC_CTX_copy(HMAC_CTX *new_ctx_p, HMACobject *self)
+{
+    int result;
+    ENTER_HASHLIB(self);
+    result = HMAC_CTX_copy(new_ctx_p, self->ctx);
+    LEAVE_HASHLIB(self);
+    return result;
+}
+
+static unsigned int
+_hmac_digest_size(HMACobject *self)
+{
+    unsigned int digest_size = EVP_MD_size(HMAC_CTX_get_md(self->ctx));
+    assert(digest_size <= EVP_MAX_MD_SIZE);
+    return digest_size;
+}
+
+static int
+_hmac_update(HMACobject *self, PyObject *obj)
+{
+    int r;
+    Py_buffer view = {0};
+
+    GET_BUFFER_VIEW_OR_ERROR(obj, &view, return 0);
+
+    if (self->lock == NULL && view.len >= HASHLIB_GIL_MINSIZE) {
+        self->lock = PyThread_allocate_lock();
+        /* fail? lock = NULL and we fail over to non-threaded code. */
+    }
+
+    if (self->lock != NULL) {
+        ENTER_HASHLIB(self);
+        r = HMAC_Update(self->ctx, (const unsigned char*)view.buf, view.len);
+        LEAVE_HASHLIB(self);
+    } else {
+        r = HMAC_Update(self->ctx, (const unsigned char*)view.buf, view.len);
+    }
+
+    PyBuffer_Release(&view);
+
+    if (r == 0) {
+        _setException(PyExc_ValueError);
+        return 0;
+    }
+    return 1;
+}
+
+/*[clinic input]
+_hashlib.HMAC.copy
+
+Return a copy ("clone") of the HMAC object.
+[clinic start generated code]*/
+
+static PyObject *
+_hashlib_HMAC_copy_impl(HMACobject *self)
+/*[clinic end generated code: output=29aa28b452833127 input=e2fa6a05db61a4d6]*/
+{
+    HMACobject *retval;
+
+    HMAC_CTX *ctx = HMAC_CTX_new();
+    if (ctx == NULL) {
+        return _setException(PyExc_ValueError);
+    }
+    if (!locked_HMAC_CTX_copy(ctx, self)) {
+        HMAC_CTX_free(ctx);
+        return _setException(PyExc_ValueError);
+    }
+
+    retval = (HMACobject *)PyObject_New(HMACobject, Py_TYPE(self));
+    if (retval == NULL) {
+        HMAC_CTX_free(ctx);
+        return NULL;
+    }
+    retval->ctx = ctx;
+    retval->lock = NULL;
+
+    return (PyObject *)retval;
+}
+
+static void
+_hmac_dealloc(HMACobject *self)
+{
+    PyTypeObject *tp = Py_TYPE(self);
+    if (self->lock != NULL) {
+        PyThread_free_lock(self->lock);
+    }
+    HMAC_CTX_free(self->ctx);
+    PyObject_Del(self);
+    Py_DECREF(tp);
+}
+
+static PyObject *
+_hmac_repr(HMACobject *self)
+{
+    PyObject *digest_name = py_digest_name(HMAC_CTX_get_md(self->ctx));
+    if (digest_name == NULL) {
+        return NULL;
+    }
+    PyObject *repr = PyUnicode_FromFormat(
+        "<%U HMAC object @ %p>", digest_name, self
+    );
+    Py_DECREF(digest_name);
+    return repr;
+}
+
+/*[clinic input]
+_hashlib.HMAC.update
+    msg: object
+
+Update the HMAC object with msg.
+[clinic start generated code]*/
+
+static PyObject *
+_hashlib_HMAC_update_impl(HMACobject *self, PyObject *msg)
+/*[clinic end generated code: output=f31f0ace8c625b00 input=1829173bb3cfd4e6]*/
+{
+    if (!_hmac_update(self, msg)) {
+        return NULL;
+    }
+    Py_RETURN_NONE;
+}
+
+static int
+_hmac_digest(HMACobject *self, unsigned char *buf, unsigned int len)
+{
+    HMAC_CTX *temp_ctx = HMAC_CTX_new();
+    if (temp_ctx == NULL) {
+        PyErr_NoMemory();
+        return 0;
+    }
+    if (!locked_HMAC_CTX_copy(temp_ctx, self)) {
+        _setException(PyExc_ValueError);
+        return 0;
+    }
+    int r = HMAC_Final(temp_ctx, buf, &len);
+    HMAC_CTX_free(temp_ctx);
+    if (r == 0) {
+        _setException(PyExc_ValueError);
+        return 0;
+    }
+    return 1;
+}
+
+/*[clinic input]
+_hashlib.HMAC.digest
+Return the digest of the bytes passed to the update() method so far.
+[clinic start generated code]*/
+
+static PyObject *
+_hashlib_HMAC_digest_impl(HMACobject *self)
+/*[clinic end generated code: output=1b1424355af7a41e input=bff07f74da318fb4]*/
+{
+    unsigned char digest[EVP_MAX_MD_SIZE];
+    unsigned int digest_size = _hmac_digest_size(self);
+    if (digest_size == 0) {
+        return _setException(PyExc_ValueError);
+    }
+    int r = _hmac_digest(self, digest, digest_size);
+    if (r == 0) {
+        return NULL;
+    }
+    return PyBytes_FromStringAndSize((const char *)digest, digest_size);
+}
+
+/*[clinic input]
+_hashlib.HMAC.hexdigest
+
+Return hexadecimal digest of the bytes passed to the update() method so far.
+
+This may be used to exchange the value safely in email or other non-binary
+environments.
+[clinic start generated code]*/
+
+static PyObject *
+_hashlib_HMAC_hexdigest_impl(HMACobject *self)
+/*[clinic end generated code: output=80d825be1eaae6a7 input=5abc42702874ddcf]*/
+{
+    unsigned char digest[EVP_MAX_MD_SIZE];
+    unsigned int digest_size = _hmac_digest_size(self);
+    if (digest_size == 0) {
+        return _setException(PyExc_ValueError);
+    }
+    int r = _hmac_digest(self, digest, digest_size);
+    if (r == 0) {
+        return NULL;
+    }
+    return _Py_strhex((const char *)digest, digest_size);
+}
+
+static PyObject *
+_hashlib_hmac_get_digest_size(HMACobject *self, void *closure)
+{
+    unsigned int digest_size = _hmac_digest_size(self);
+    if (digest_size == 0) {
+        return _setException(PyExc_ValueError);
+    }
+    return PyLong_FromLong(digest_size);
+}
+
+static PyObject *
+_hashlib_hmac_get_block_size(HMACobject *self, void *closure)
+{
+    const EVP_MD *md = HMAC_CTX_get_md(self->ctx);
+    if (md == NULL) {
+        return _setException(PyExc_ValueError);
+    }
+    return PyLong_FromLong(EVP_MD_block_size(md));
+}
+
+static PyObject *
+_hashlib_hmac_get_name(HMACobject *self, void *closure)
+{
+    PyObject *digest_name = py_digest_name(HMAC_CTX_get_md(self->ctx));
+    if (digest_name == NULL) {
+        return NULL;
+    }
+    PyObject *name = PyUnicode_FromFormat("hmac-%U", digest_name);
+    Py_DECREF(digest_name);
+    return name;
+}
+
+static PyMethodDef HMAC_methods[] = {
+    _HASHLIB_HMAC_UPDATE_METHODDEF
+    _HASHLIB_HMAC_DIGEST_METHODDEF
+    _HASHLIB_HMAC_HEXDIGEST_METHODDEF
+    _HASHLIB_HMAC_COPY_METHODDEF
+    {NULL, NULL}  /* sentinel */
+};
+
+static PyGetSetDef HMAC_getset[] = {
+    {"digest_size", (getter)_hashlib_hmac_get_digest_size, NULL, NULL, NULL},
+    {"block_size", (getter)_hashlib_hmac_get_block_size, NULL, NULL, NULL},
+    {"name", (getter)_hashlib_hmac_get_name, NULL, NULL, NULL},
+    {NULL}  /* Sentinel */
+};
+
+
+PyDoc_STRVAR(hmactype_doc,
+"The object used to calculate HMAC of a message.\n\
+\n\
+Methods:\n\
+\n\
+update() -- updates the current digest with an additional string\n\
+digest() -- return the current digest value\n\
+hexdigest() -- return the current digest as a string of hexadecimal digits\n\
+copy() -- return a copy of the current hash object\n\
+\n\
+Attributes:\n\
+\n\
+name -- the name, including the hash algorithm used by this object\n\
+digest_size -- number of bytes in digest() output\n");
+
+static PyType_Slot HMACtype_slots[] = {
+    {Py_tp_doc, (char *)hmactype_doc},
+    {Py_tp_repr, (reprfunc)_hmac_repr},
+    {Py_tp_dealloc,(destructor)_hmac_dealloc},
+    {Py_tp_methods, HMAC_methods},
+    {Py_tp_getset, HMAC_getset},
+    {Py_tp_new, _disabled_new},
+    {0, NULL}
+};
+
+PyType_Spec HMACtype_spec = {
+    "_hashlib.HMAC",    /* name */
+    sizeof(HMACobject),     /* basicsize */
+    .flags = Py_TPFLAGS_DEFAULT,
+    .slots = HMACtype_slots,
+};
+
+
 /* State for our callback function so that it can accumulate a result. */
 typedef struct _internal_name_mapper_state {
     PyObject *set;
@@ -1448,7 +1829,8 @@ static struct PyMethodDef EVP_functions[] = {
     PBKDF2_HMAC_METHODDEF
     _HASHLIB_SCRYPT_METHODDEF
     _HASHLIB_GET_FIPS_MODE_METHODDEF
-    _HASHLIB_HMAC_DIGEST_METHODDEF
+    _HASHLIB_HMAC_SINGLESHOT_METHODDEF
+    _HASHLIB_HMAC_NEW_METHODDEF
     _HASHLIB_OPENSSL_MD5_METHODDEF
     _HASHLIB_OPENSSL_SHA1_METHODDEF
     _HASHLIB_OPENSSL_SHA224_METHODDEF
@@ -1472,6 +1854,7 @@ hashlib_traverse(PyObject *m, visitproc visit, void *arg)
 {
     _hashlibstate *state = get_hashlib_state(m);
     Py_VISIT(state->EVPtype);
+    Py_VISIT(state->HMACtype);
 #ifdef PY_OPENSSL_HAS_SHAKE
     Py_VISIT(state->EVPXOFtype);
 #endif
@@ -1483,6 +1866,7 @@ hashlib_clear(PyObject *m)
 {
     _hashlibstate *state = get_hashlib_state(m);
     Py_CLEAR(state->EVPtype);
+    Py_CLEAR(state->HMACtype);
 #ifdef PY_OPENSSL_HAS_SHAKE
     Py_CLEAR(state->EVPXOFtype);
 #endif
@@ -1541,10 +1925,18 @@ PyInit__hashlib(void)
         return NULL;
     }
     state->EVPtype = EVPtype;
-
     Py_INCREF((PyObject *)state->EVPtype);
     PyModule_AddObject(m, "HASH", (PyObject *)state->EVPtype);
 
+    PyTypeObject *HMACtype = (PyTypeObject *)PyType_FromSpec(&HMACtype_spec);
+    if (HMACtype == NULL) {
+        Py_DECREF(m);
+        return NULL;
+    }
+    state->HMACtype = HMACtype;
+    Py_INCREF((PyObject *)state->HMACtype);
+    PyModule_AddObject(m, "HMAC", (PyObject *)state->HMACtype);
+
 #ifdef PY_OPENSSL_HAS_SHAKE
     bases = PyTuple_Pack(1, (PyObject *)EVPtype);
     if (bases == NULL) {
diff --git a/Modules/clinic/_hashopenssl.c.h b/Modules/clinic/_hashopenssl.c.h
index 71c9246c95a93..8745fc7052ba1 100644
--- a/Modules/clinic/_hashopenssl.c.h
+++ b/Modules/clinic/_hashopenssl.c.h
@@ -1095,21 +1095,21 @@ _hashlib_scrypt(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObj
 
 #endif /* (OPENSSL_VERSION_NUMBER > 0x10100000L && !defined(OPENSSL_NO_SCRYPT) && !defined(LIBRESSL_VERSION_NUMBER)) */
 
-PyDoc_STRVAR(_hashlib_hmac_digest__doc__,
+PyDoc_STRVAR(_hashlib_hmac_singleshot__doc__,
 "hmac_digest($module, /, key, msg, digest)\n"
 "--\n"
 "\n"
 "Single-shot HMAC.");
 
-#define _HASHLIB_HMAC_DIGEST_METHODDEF    \
-    {"hmac_digest", (PyCFunction)(void(*)(void))_hashlib_hmac_digest, METH_FASTCALL|METH_KEYWORDS, _hashlib_hmac_digest__doc__},
+#define _HASHLIB_HMAC_SINGLESHOT_METHODDEF    \
+    {"hmac_digest", (PyCFunction)(void(*)(void))_hashlib_hmac_singleshot, METH_FASTCALL|METH_KEYWORDS, _hashlib_hmac_singleshot__doc__},
 
 static PyObject *
-_hashlib_hmac_digest_impl(PyObject *module, Py_buffer *key, Py_buffer *msg,
-                          const char *digest);
+_hashlib_hmac_singleshot_impl(PyObject *module, Py_buffer *key,
+                              Py_buffer *msg, const char *digest);
 
 static PyObject *
-_hashlib_hmac_digest(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
+_hashlib_hmac_singleshot(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
 {
     PyObject *return_value = NULL;
     static const char * const _keywords[] = {"key", "msg", "digest", NULL};
@@ -1150,7 +1150,7 @@ _hashlib_hmac_digest(PyObject *module, PyObject *const *args, Py_ssize_t nargs,
         PyErr_SetString(PyExc_ValueError, "embedded null character");
         goto exit;
     }
-    return_value = _hashlib_hmac_digest_impl(module, &key, &msg, digest);
+    return_value = _hashlib_hmac_singleshot_impl(module, &key, &msg, digest);
 
 exit:
     /* Cleanup for key */
@@ -1165,6 +1165,165 @@ _hashlib_hmac_digest(PyObject *module, PyObject *const *args, Py_ssize_t nargs,
     return return_value;
 }
 
+PyDoc_STRVAR(_hashlib_hmac_new__doc__,
+"hmac_new($module, /, key, msg=b\'\', digestmod=None)\n"
+"--\n"
+"\n"
+"Return a new hmac object.");
+
+#define _HASHLIB_HMAC_NEW_METHODDEF    \
+    {"hmac_new", (PyCFunction)(void(*)(void))_hashlib_hmac_new, METH_FASTCALL|METH_KEYWORDS, _hashlib_hmac_new__doc__},
+
+static PyObject *
+_hashlib_hmac_new_impl(PyObject *module, Py_buffer *key, PyObject *msg_obj,
+                       const char *digestmod);
+
+static PyObject *
+_hashlib_hmac_new(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
+{
+    PyObject *return_value = NULL;
+    static const char * const _keywords[] = {"key", "msg", "digestmod", NULL};
+    static _PyArg_Parser _parser = {NULL, _keywords, "hmac_new", 0};
+    PyObject *argsbuf[3];
+    Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1;
+    Py_buffer key = {NULL, NULL};
+    PyObject *msg_obj = NULL;
+    const char *digestmod = NULL;
+
+    args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 3, 0, argsbuf);
+    if (!args) {
+        goto exit;
+    }
+    if (PyObject_GetBuffer(args[0], &key, PyBUF_SIMPLE) != 0) {
+        goto exit;
+    }
+    if (!PyBuffer_IsContiguous(&key, 'C')) {
+        _PyArg_BadArgument("hmac_new", "argument 'key'", "contiguous buffer", args[0]);
+        goto exit;
+    }
+    if (!noptargs) {
+        goto skip_optional_pos;
+    }
+    if (args[1]) {
+        msg_obj = args[1];
+        if (!--noptargs) {
+            goto skip_optional_pos;
+        }
+    }
+    if (!PyUnicode_Check(args[2])) {
+        _PyArg_BadArgument("hmac_new", "argument 'digestmod'", "str", args[2]);
+        goto exit;
+    }
+    Py_ssize_t digestmod_length;
+    digestmod = PyUnicode_AsUTF8AndSize(args[2], &digestmod_length);
+    if (digestmod == NULL) {
+        goto exit;
+    }
+    if (strlen(digestmod) != (size_t)digestmod_length) {
+        PyErr_SetString(PyExc_ValueError, "embedded null character");
+        goto exit;
+    }
+skip_optional_pos:
+    return_value = _hashlib_hmac_new_impl(module, &key, msg_obj, digestmod);
+
+exit:
+    /* Cleanup for key */
+    if (key.obj) {
+       PyBuffer_Release(&key);
+    }
+
+    return return_value;
+}
+
+PyDoc_STRVAR(_hashlib_HMAC_copy__doc__,
+"copy($self, /)\n"
+"--\n"
+"\n"
+"Return a copy (\"clone\") of the HMAC object.");
+
+#define _HASHLIB_HMAC_COPY_METHODDEF    \
+    {"copy", (PyCFunction)_hashlib_HMAC_copy, METH_NOARGS, _hashlib_HMAC_copy__doc__},
+
+static PyObject *
+_hashlib_HMAC_copy_impl(HMACobject *self);
+
+static PyObject *
+_hashlib_HMAC_copy(HMACobject *self, PyObject *Py_UNUSED(ignored))
+{
+    return _hashlib_HMAC_copy_impl(self);
+}
+
+PyDoc_STRVAR(_hashlib_HMAC_update__doc__,
+"update($self, /, msg)\n"
+"--\n"
+"\n"
+"Update the HMAC object with msg.");
+
+#define _HASHLIB_HMAC_UPDATE_METHODDEF    \
+    {"update", (PyCFunction)(void(*)(void))_hashlib_HMAC_update, METH_FASTCALL|METH_KEYWORDS, _hashlib_HMAC_update__doc__},
+
+static PyObject *
+_hashlib_HMAC_update_impl(HMACobject *self, PyObject *msg);
+
+static PyObject *
+_hashlib_HMAC_update(HMACobject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
+{
+    PyObject *return_value = NULL;
+    static const char * const _keywords[] = {"msg", NULL};
+    static _PyArg_Parser _parser = {NULL, _keywords, "update", 0};
+    PyObject *argsbuf[1];
+    PyObject *msg;
+
+    args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf);
+    if (!args) {
+        goto exit;
+    }
+    msg = args[0];
+    return_value = _hashlib_HMAC_update_impl(self, msg);
+
+exit:
+    return return_value;
+}
+
+PyDoc_STRVAR(_hashlib_HMAC_digest__doc__,
+"digest($self, /)\n"
+"--\n"
+"\n"
+"Return the digest of the bytes passed to the update() method so far.");
+
+#define _HASHLIB_HMAC_DIGEST_METHODDEF    \
+    {"digest", (PyCFunction)_hashlib_HMAC_digest, METH_NOARGS, _hashlib_HMAC_digest__doc__},
+
+static PyObject *
+_hashlib_HMAC_digest_impl(HMACobject *self);
+
+static PyObject *
+_hashlib_HMAC_digest(HMACobject *self, PyObject *Py_UNUSED(ignored))
+{
+    return _hashlib_HMAC_digest_impl(self);
+}
+
+PyDoc_STRVAR(_hashlib_HMAC_hexdigest__doc__,
+"hexdigest($self, /)\n"
+"--\n"
+"\n"
+"Return hexadecimal digest of the bytes passed to the update() method so far.\n"
+"\n"
+"This may be used to exchange the value safely in email or other non-binary\n"
+"environments.");
+
+#define _HASHLIB_HMAC_HEXDIGEST_METHODDEF    \
+    {"hexdigest", (PyCFunction)_hashlib_HMAC_hexdigest, METH_NOARGS, _hashlib_HMAC_hexdigest__doc__},
+
+static PyObject *
+_hashlib_HMAC_hexdigest_impl(HMACobject *self);
+
+static PyObject *
+_hashlib_HMAC_hexdigest(HMACobject *self, PyObject *Py_UNUSED(ignored))
+{
+    return _hashlib_HMAC_hexdigest_impl(self);
+}
+
 #if !defined(LIBRESSL_VERSION_NUMBER)
 
 PyDoc_STRVAR(_hashlib_get_fips_mode__doc__,
@@ -1243,4 +1402,4 @@ _hashlib_get_fips_mode(PyObject *module, PyObject *Py_UNUSED(ignored))
 #ifndef _HASHLIB_GET_FIPS_MODE_METHODDEF
     #define _HASHLIB_GET_FIPS_MODE_METHODDEF
 #endif /* !defined(_HASHLIB_GET_FIPS_MODE_METHODDEF) */
-/*[clinic end generated code: output=a39bf0a766d7cdf7 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=972a198d2e8434bd input=a9049054013a1b77]*/



More information about the Python-checkins mailing list