summaryrefslogtreecommitdiffstats
path: root/Modules/operator.c
diff options
context:
space:
mode:
authorChristian Heimes <christian@cheimes.de>2012-06-24 11:48:32 (GMT)
committerChristian Heimes <christian@cheimes.de>2012-06-24 11:48:32 (GMT)
commit6cea65555caf2716b4633827715004ab0291a282 (patch)
tree5ddf9676293edcc5086bd17c4ad432b175888ebf /Modules/operator.c
parent605a62ddb1c19978ee194a40a458f072e3242a31 (diff)
downloadcpython-6cea65555caf2716b4633827715004ab0291a282.zip
cpython-6cea65555caf2716b4633827715004ab0291a282.tar.gz
cpython-6cea65555caf2716b4633827715004ab0291a282.tar.bz2
Issue #15061: Re-implemented hmac.compare_digest() in C
Diffstat (limited to 'Modules/operator.c')
-rw-r--r--Modules/operator.c142
1 files changed, 142 insertions, 0 deletions
diff --git a/Modules/operator.c b/Modules/operator.c
index 2f47573..c38ec16 100644
--- a/Modules/operator.c
+++ b/Modules/operator.c
@@ -159,6 +159,146 @@ is_not(PyObject *s, PyObject *a)
#undef spam2
#undef spam1o
#undef spam1o
+
+/* compare_digest **********************************************************/
+
+/*
+ * timing safe compare
+ *
+ * Returns 1 of the strings are equal.
+ * In case of len(a) != len(b) the function tries to keep the timing
+ * dependent on the length of b. CPU cache locally may still alter timing
+ * a bit.
+ */
+static int
+_tscmp(const unsigned char *a, const unsigned char *b,
+ Py_ssize_t len_a, Py_ssize_t len_b)
+{
+ /* The volatile type declarations make sure that the compiler has no
+ * chance to optimize and fold the code in any way that may change
+ * the timing.
+ */
+ volatile Py_ssize_t length;
+ volatile const unsigned char *left;
+ volatile const unsigned char *right;
+ Py_ssize_t i;
+ unsigned char result;
+
+ /* loop count depends on length of b */
+ length = len_b;
+ left = NULL;
+ right = b;
+
+ /* don't use else here to keep the amount of CPU instructions constant,
+ * volatile forces re-evaluation
+ * */
+ if (len_a == length) {
+ left = *((volatile const unsigned char**)&a);
+ result = 0;
+ }
+ if (len_a != length) {
+ left = b;
+ result = 1;
+ }
+
+ for (i=0; i < length; i++) {
+ result |= *left++ ^ *right++;
+ }
+
+ return (result == 0);
+}
+
+PyDoc_STRVAR(compare_digest__doc__,
+"compare_digest(a, b) -> bool\n"
+"\n"
+"Return the equivalent of 'a == b', but avoid any short circuiting to\n"
+"counterfeit timing analysis of input data. The function should be used to\n"
+"compare cryptographic secrets. a and b must both either support the buffer\n"
+"protocol (e.g. bytes) or be ASCII only str instances at the same time.\n"
+"\n"
+"Note: In case of an error or different lengths the function may disclose\n"
+"some timing information about the types and lengths of a and b.\n");
+
+
+static PyObject*
+compare_digest(PyObject *self, PyObject *args)
+{
+ PyObject *a, *b;
+ int rc;
+ PyObject *result;
+
+ if (!PyArg_ParseTuple(args, "OO:compare_digest", &a, &b)) {
+ return NULL;
+ }
+
+ /* 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);
+ }
+
+ result = PyBool_FromLong(rc);
+ Py_INCREF(result);
+ return result;
+}
+
+/* operator methods **********************************************************/
+
#define spam1(OP,DOC) {#OP, OP, METH_VARARGS, PyDoc_STR(DOC)},
#define spam2(OP,ALTOP,DOC) {#OP, op_##OP, METH_VARARGS, PyDoc_STR(DOC)}, \
{#ALTOP, op_##OP, METH_VARARGS, PyDoc_STR(DOC)},
@@ -227,6 +367,8 @@ spam2(ne,__ne__, "ne(a, b) -- Same as a!=b.")
spam2(gt,__gt__, "gt(a, b) -- Same as a>b.")
spam2(ge,__ge__, "ge(a, b) -- Same as a>=b.")
+ {"_compare_digest", (PyCFunction)compare_digest, METH_VARARGS,
+ compare_digest__doc__},
{NULL, NULL} /* sentinel */
};