diff options
author | 2020-05-28 14:09:38 +0200 | |
---|---|---|
committer | 2020-05-28 05:09:38 -0700 | |
commit | 8183e11d87388e4e44e3242c42085b87a878f781 (patch) | |
tree | 0b0302ad36f4a4b67a64eb68dc5b280ce84f90f7 /Modules | |
parent | Improve IO tutorial's "Old string formatting" section (GH-16251) (diff) | |
download | cpython-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.c | 116 | ||||
-rw-r--r-- | Modules/_operator.c | 2 | ||||
-rw-r--r-- | Modules/clinic/_hashopenssl.c.h | 42 |
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]*/ |