summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Heimes <christian@python.org>2020-05-28 14:09:38 +0200
committerGitHub <noreply@github.com>2020-05-28 05:09:38 -0700
commit8183e11d87388e4e44e3242c42085b87a878f781 (patch)
tree0b0302ad36f4a4b67a64eb68dc5b280ce84f90f7 /Modules
parentImprove IO tutorial's "Old string formatting" section (GH-16251) (diff)
downloadcpython-8183e11d87388e4e44e3242c42085b87a878f781.tar.gz
cpython-8183e11d87388e4e44e3242c42085b87a878f781.tar.bz2
cpython-8183e11d87388e4e44e3242c42085b87a878f781.zip
[3.9] bpo-40791: Use CRYPTO_memcmp() for compare_digest (GH-20456) (GH-20461)
hashlib.compare_digest uses OpenSSL's CRYPTO_memcmp() function when OpenSSL is available. Note: The _operator module is a builtin module. I don't want to add libcrypto dependency to libpython. Therefore I duplicated the wrapper function and added a copy to _hashopenssl.c.. (cherry picked from commit db5aed931f8a617f7b63e773f62db468fe9c5ca1) Co-authored-by: Christian Heimes <christian@python.org>
Diffstat (limited to 'Modules')
-rw-r--r--Modules/_hashopenssl.c116
-rw-r--r--Modules/_operator.c2
-rw-r--r--Modules/clinic/_hashopenssl.c.h42
3 files changed, 159 insertions, 1 deletions
diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c
index 0b2ef95a6f1..adc86537732 100644
--- a/Modules/_hashopenssl.c
+++ b/Modules/_hashopenssl.c
@@ -21,6 +21,7 @@
/* EVP is the preferred interface to hashing in OpenSSL */
#include <openssl/evp.h>
#include <openssl/hmac.h>
+#include <openssl/crypto.h>
/* We use the object interface to discover what hashes OpenSSL supports. */
#include <openssl/objects.h>
#include "openssl/err.h"
@@ -1833,6 +1834,120 @@ _hashlib_get_fips_mode_impl(PyObject *module)
#endif // !LIBRESSL_VERSION_NUMBER
+static int
+_tscmp(const unsigned char *a, const unsigned char *b,
+ Py_ssize_t len_a, Py_ssize_t len_b)
+{
+ /* loop count depends on length of b. Might leak very little timing
+ * information if sizes are different.
+ */
+ Py_ssize_t length = len_b;
+ const void *left = a;
+ const void *right = b;
+ int result = 0;
+
+ if (len_a != length) {
+ left = b;
+ result = 1;
+ }
+
+ result |= CRYPTO_memcmp(left, right, length);
+
+ return (result == 0);
+}
+
+/* NOTE: Keep in sync with _operator.c implementation. */
+
+/*[clinic input]
+_hashlib.compare_digest
+
+ a: object
+ b: object
+ /
+
+Return 'a == b'.
+
+This function uses an approach designed to prevent
+timing analysis, making it appropriate for cryptography.
+
+a and b must both be of the same type: either str (ASCII only),
+or any bytes-like object.
+
+Note: If a and b are of different lengths, or if an error occurs,
+a timing attack could theoretically reveal information about the
+types and lengths of a and b--but not their values.
+[clinic start generated code]*/
+
+static PyObject *
+_hashlib_compare_digest_impl(PyObject *module, PyObject *a, PyObject *b)
+/*[clinic end generated code: output=6f1c13927480aed9 input=9c40c6e566ca12f5]*/
+{
+ int rc;
+
+ /* ASCII unicode string */
+ if(PyUnicode_Check(a) && PyUnicode_Check(b)) {
+ if (PyUnicode_READY(a) == -1 || PyUnicode_READY(b) == -1) {
+ return NULL;
+ }
+ if (!PyUnicode_IS_ASCII(a) || !PyUnicode_IS_ASCII(b)) {
+ PyErr_SetString(PyExc_TypeError,
+ "comparing strings with non-ASCII characters is "
+ "not supported");
+ return NULL;
+ }
+
+ rc = _tscmp(PyUnicode_DATA(a),
+ PyUnicode_DATA(b),
+ PyUnicode_GET_LENGTH(a),
+ PyUnicode_GET_LENGTH(b));
+ }
+ /* fallback to buffer interface for bytes, bytesarray and other */
+ else {
+ Py_buffer view_a;
+ Py_buffer view_b;
+
+ if (PyObject_CheckBuffer(a) == 0 && PyObject_CheckBuffer(b) == 0) {
+ PyErr_Format(PyExc_TypeError,
+ "unsupported operand types(s) or combination of types: "
+ "'%.100s' and '%.100s'",
+ Py_TYPE(a)->tp_name, Py_TYPE(b)->tp_name);
+ return NULL;
+ }
+
+ if (PyObject_GetBuffer(a, &view_a, PyBUF_SIMPLE) == -1) {
+ return NULL;
+ }
+ if (view_a.ndim > 1) {
+ PyErr_SetString(PyExc_BufferError,
+ "Buffer must be single dimension");
+ PyBuffer_Release(&view_a);
+ return NULL;
+ }
+
+ if (PyObject_GetBuffer(b, &view_b, PyBUF_SIMPLE) == -1) {
+ PyBuffer_Release(&view_a);
+ return NULL;
+ }
+ if (view_b.ndim > 1) {
+ PyErr_SetString(PyExc_BufferError,
+ "Buffer must be single dimension");
+ PyBuffer_Release(&view_a);
+ PyBuffer_Release(&view_b);
+ return NULL;
+ }
+
+ rc = _tscmp((const unsigned char*)view_a.buf,
+ (const unsigned char*)view_b.buf,
+ view_a.len,
+ view_b.len);
+
+ PyBuffer_Release(&view_a);
+ PyBuffer_Release(&view_b);
+ }
+
+ return PyBool_FromLong(rc);
+}
+
/* List of functions exported by this module */
static struct PyMethodDef EVP_functions[] = {
@@ -1840,6 +1955,7 @@ static struct PyMethodDef EVP_functions[] = {
PBKDF2_HMAC_METHODDEF
_HASHLIB_SCRYPT_METHODDEF
_HASHLIB_GET_FIPS_MODE_METHODDEF
+ _HASHLIB_COMPARE_DIGEST_METHODDEF
_HASHLIB_HMAC_SINGLESHOT_METHODDEF
_HASHLIB_HMAC_NEW_METHODDEF
_HASHLIB_OPENSSL_MD5_METHODDEF
diff --git a/Modules/_operator.c b/Modules/_operator.c
index 19026b6c38e..8a54829e5bb 100644
--- a/Modules/_operator.c
+++ b/Modules/_operator.c
@@ -785,6 +785,8 @@ _operator_length_hint_impl(PyObject *module, PyObject *obj,
return PyObject_LengthHint(obj, default_value);
}
+/* NOTE: Keep in sync with _hashopenssl.c implementation. */
+
/*[clinic input]
_operator._compare_digest = _operator.eq
diff --git a/Modules/clinic/_hashopenssl.c.h b/Modules/clinic/_hashopenssl.c.h
index 5ab4e996bf9..68aa765e529 100644
--- a/Modules/clinic/_hashopenssl.c.h
+++ b/Modules/clinic/_hashopenssl.c.h
@@ -1363,6 +1363,46 @@ exit:
#endif /* !defined(LIBRESSL_VERSION_NUMBER) */
+PyDoc_STRVAR(_hashlib_compare_digest__doc__,
+"compare_digest($module, a, b, /)\n"
+"--\n"
+"\n"
+"Return \'a == b\'.\n"
+"\n"
+"This function uses an approach designed to prevent\n"
+"timing analysis, making it appropriate for cryptography.\n"
+"\n"
+"a and b must both be of the same type: either str (ASCII only),\n"
+"or any bytes-like object.\n"
+"\n"
+"Note: If a and b are of different lengths, or if an error occurs,\n"
+"a timing attack could theoretically reveal information about the\n"
+"types and lengths of a and b--but not their values.");
+
+#define _HASHLIB_COMPARE_DIGEST_METHODDEF \
+ {"compare_digest", (PyCFunction)(void(*)(void))_hashlib_compare_digest, METH_FASTCALL, _hashlib_compare_digest__doc__},
+
+static PyObject *
+_hashlib_compare_digest_impl(PyObject *module, PyObject *a, PyObject *b);
+
+static PyObject *
+_hashlib_compare_digest(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
+{
+ PyObject *return_value = NULL;
+ PyObject *a;
+ PyObject *b;
+
+ if (!_PyArg_CheckPositional("compare_digest", nargs, 2, 2)) {
+ goto exit;
+ }
+ a = args[0];
+ b = args[1];
+ return_value = _hashlib_compare_digest_impl(module, a, b);
+
+exit:
+ return return_value;
+}
+
#ifndef EVPXOF_DIGEST_METHODDEF
#define EVPXOF_DIGEST_METHODDEF
#endif /* !defined(EVPXOF_DIGEST_METHODDEF) */
@@ -1402,4 +1442,4 @@ exit:
#ifndef _HASHLIB_GET_FIPS_MODE_METHODDEF
#define _HASHLIB_GET_FIPS_MODE_METHODDEF
#endif /* !defined(_HASHLIB_GET_FIPS_MODE_METHODDEF) */
-/*[clinic end generated code: output=a0bff5dcef88de6a input=a9049054013a1b77]*/
+/*[clinic end generated code: output=b6b280e46bf0b139 input=a9049054013a1b77]*/