Blame SOURCES/00209-pep466-backport-hmac.compare_digest.patch

f63228
f63228
# HG changeset patch
f63228
# User Benjamin Peterson <benjamin@python.org>
f63228
# Date 1399849904 25200
f63228
# Node ID b40f1a00b13460cc089450028280c4e52dd24a64
f63228
# Parent  951775c68b1b7782750c213b0fce1f61d46b2f51
f63228
backport hmac.compare_digest to partially implement PEP 466 (closes #21306)
f63228
f63228
Backport from Alex Gaynor.
f63228
f63228
diff --git a/Doc/library/hmac.rst b/Doc/library/hmac.rst
f63228
--- a/Doc/library/hmac.rst
f63228
+++ b/Doc/library/hmac.rst
f63228
@@ -38,6 +38,13 @@ An HMAC object has the following methods
f63228
    This string will be the same length as the *digest_size* of the digest given to
f63228
    the constructor.  It may contain non-ASCII characters, including NUL bytes.
f63228
 
f63228
+   .. warning::
f63228
+
f63228
+      When comparing the output of :meth:`digest` to an externally-supplied
f63228
+      digest during a verification routine, it is recommended to use the
f63228
+      :func:`compare_digest` function instead of the ``==`` operator
f63228
+      to reduce the vulnerability to timing attacks.
f63228
+
f63228
 
f63228
 .. method:: HMAC.hexdigest()
f63228
 
f63228
@@ -45,6 +52,13 @@ An HMAC object has the following methods
f63228
    containing only hexadecimal digits.  This may be used to exchange the value
f63228
    safely in email or other non-binary environments.
f63228
 
f63228
+   .. warning::
f63228
+
f63228
+      When comparing the output of :meth:`hexdigest` to an externally-supplied
f63228
+      digest during a verification routine, it is recommended to use the
f63228
+      :func:`compare_digest` function instead of the ``==`` operator
f63228
+      to reduce the vulnerability to timing attacks.
f63228
+
f63228
 
f63228
 .. method:: HMAC.copy()
f63228
 
f63228
@@ -52,6 +66,25 @@ An HMAC object has the following methods
f63228
    compute the digests of strings that share a common initial substring.
f63228
 
f63228
 
f63228
+This module also provides the following helper function:
f63228
+
f63228
+.. function:: compare_digest(a, b)
f63228
+
f63228
+   Return ``a == b``.  This function uses an approach designed to prevent
f63228
+   timing analysis by avoiding content-based short circuiting behaviour,
f63228
+   making it appropriate for cryptography.  *a* and *b* must both be of the
f63228
+   same type: either :class:`unicode` or a :term:`bytes-like object`.
f63228
+
f63228
+   .. note::
f63228
+
f63228
+      If *a* and *b* are of different lengths, or if an error occurs,
f63228
+      a timing attack could theoretically reveal information about the
f63228
+      types and lengths of *a* and *b*--but not their values.
f63228
+
f63228
+
f63228
+   .. versionadded:: 2.7.7
f63228
+
f63228
+
f63228
 .. seealso::
f63228
 
f63228
    Module :mod:`hashlib`
f63228
diff --git a/Lib/hmac.py b/Lib/hmac.py
f63228
--- a/Lib/hmac.py
f63228
+++ b/Lib/hmac.py
f63228
@@ -5,6 +5,9 @@ Implements the HMAC algorithm as describ
f63228
 
f63228
 import warnings as _warnings
f63228
 
f63228
+from operator import _compare_digest as compare_digest
f63228
+
f63228
+
f63228
 trans_5C = "".join ([chr (x ^ 0x5C) for x in xrange(256)])
f63228
 trans_36 = "".join ([chr (x ^ 0x36) for x in xrange(256)])
f63228
 
f63228
diff --git a/Lib/test/test_hmac.py b/Lib/test/test_hmac.py
f63228
--- a/Lib/test/test_hmac.py
f63228
+++ b/Lib/test/test_hmac.py
f63228
@@ -302,12 +302,122 @@ class CopyTestCase(unittest.TestCase):
f63228
         self.assertTrue(h1.hexdigest() == h2.hexdigest(),
f63228
             "Hexdigest of copy doesn't match original hexdigest.")
f63228
 
f63228
+
f63228
+class CompareDigestTestCase(unittest.TestCase):
f63228
+
f63228
+    def test_compare_digest(self):
f63228
+        # Testing input type exception handling
f63228
+        a, b = 100, 200
f63228
+        self.assertRaises(TypeError, hmac.compare_digest, a, b)
f63228
+        a, b = 100, b"foobar"
f63228
+        self.assertRaises(TypeError, hmac.compare_digest, a, b)
f63228
+        a, b = b"foobar", 200
f63228
+        self.assertRaises(TypeError, hmac.compare_digest, a, b)
f63228
+        a, b = u"foobar", b"foobar"
f63228
+        self.assertRaises(TypeError, hmac.compare_digest, a, b)
f63228
+        a, b = b"foobar", u"foobar"
f63228
+        self.assertRaises(TypeError, hmac.compare_digest, a, b)
f63228
+
f63228
+        # Testing bytes of different lengths
f63228
+        a, b = b"foobar", b"foo"
f63228
+        self.assertFalse(hmac.compare_digest(a, b))
f63228
+        a, b = b"\xde\xad\xbe\xef", b"\xde\xad"
f63228
+        self.assertFalse(hmac.compare_digest(a, b))
f63228
+
f63228
+        # Testing bytes of same lengths, different values
f63228
+        a, b = b"foobar", b"foobaz"
f63228
+        self.assertFalse(hmac.compare_digest(a, b))
f63228
+        a, b = b"\xde\xad\xbe\xef", b"\xab\xad\x1d\xea"
f63228
+        self.assertFalse(hmac.compare_digest(a, b))
f63228
+
f63228
+        # Testing bytes of same lengths, same values
f63228
+        a, b = b"foobar", b"foobar"
f63228
+        self.assertTrue(hmac.compare_digest(a, b))
f63228
+        a, b = b"\xde\xad\xbe\xef", b"\xde\xad\xbe\xef"
f63228
+        self.assertTrue(hmac.compare_digest(a, b))
f63228
+
f63228
+        # Testing bytearrays of same lengths, same values
f63228
+        a, b = bytearray(b"foobar"), bytearray(b"foobar")
f63228
+        self.assertTrue(hmac.compare_digest(a, b))
f63228
+
f63228
+        # Testing bytearrays of diffeent lengths
f63228
+        a, b = bytearray(b"foobar"), bytearray(b"foo")
f63228
+        self.assertFalse(hmac.compare_digest(a, b))
f63228
+
f63228
+        # Testing bytearrays of same lengths, different values
f63228
+        a, b = bytearray(b"foobar"), bytearray(b"foobaz")
f63228
+        self.assertFalse(hmac.compare_digest(a, b))
f63228
+
f63228
+        # Testing byte and bytearray of same lengths, same values
f63228
+        a, b = bytearray(b"foobar"), b"foobar"
f63228
+        self.assertTrue(hmac.compare_digest(a, b))
f63228
+        self.assertTrue(hmac.compare_digest(b, a))
f63228
+
f63228
+        # Testing byte bytearray of diffeent lengths
f63228
+        a, b = bytearray(b"foobar"), b"foo"
f63228
+        self.assertFalse(hmac.compare_digest(a, b))
f63228
+        self.assertFalse(hmac.compare_digest(b, a))
f63228
+
f63228
+        # Testing byte and bytearray of same lengths, different values
f63228
+        a, b = bytearray(b"foobar"), b"foobaz"
f63228
+        self.assertFalse(hmac.compare_digest(a, b))
f63228
+        self.assertFalse(hmac.compare_digest(b, a))
f63228
+
f63228
+        # Testing str of same lengths
f63228
+        a, b = "foobar", "foobar"
f63228
+        self.assertTrue(hmac.compare_digest(a, b))
f63228
+
f63228
+        # Testing str of diffeent lengths
f63228
+        a, b = "foo", "foobar"
f63228
+        self.assertFalse(hmac.compare_digest(a, b))
f63228
+
f63228
+        # Testing bytes of same lengths, different values
f63228
+        a, b = "foobar", "foobaz"
f63228
+        self.assertFalse(hmac.compare_digest(a, b))
f63228
+
f63228
+        # Testing error cases
f63228
+        a, b = u"foobar", b"foobar"
f63228
+        self.assertRaises(TypeError, hmac.compare_digest, a, b)
f63228
+        a, b = b"foobar", u"foobar"
f63228
+        self.assertRaises(TypeError, hmac.compare_digest, a, b)
f63228
+        a, b = b"foobar", 1
f63228
+        self.assertRaises(TypeError, hmac.compare_digest, a, b)
f63228
+        a, b = 100, 200
f63228
+        self.assertRaises(TypeError, hmac.compare_digest, a, b)
f63228
+        a, b = "fooä", "fooä"
f63228
+        self.assertTrue(hmac.compare_digest(a, b))
f63228
+
f63228
+        # subclasses are supported by ignore __eq__
f63228
+        class mystr(str):
f63228
+            def __eq__(self, other):
f63228
+                return False
f63228
+
f63228
+        a, b = mystr("foobar"), mystr("foobar")
f63228
+        self.assertTrue(hmac.compare_digest(a, b))
f63228
+        a, b = mystr("foobar"), "foobar"
f63228
+        self.assertTrue(hmac.compare_digest(a, b))
f63228
+        a, b = mystr("foobar"), mystr("foobaz")
f63228
+        self.assertFalse(hmac.compare_digest(a, b))
f63228
+
f63228
+        class mybytes(bytes):
f63228
+            def __eq__(self, other):
f63228
+                return False
f63228
+
f63228
+        a, b = mybytes(b"foobar"), mybytes(b"foobar")
f63228
+        self.assertTrue(hmac.compare_digest(a, b))
f63228
+        a, b = mybytes(b"foobar"), b"foobar"
f63228
+        self.assertTrue(hmac.compare_digest(a, b))
f63228
+        a, b = mybytes(b"foobar"), mybytes(b"foobaz")
f63228
+        self.assertFalse(hmac.compare_digest(a, b))
f63228
+
f63228
+
f63228
 def test_main():
f63228
     test_support.run_unittest(
f63228
         TestVectorsTestCase,
f63228
         ConstructorTestCase,
f63228
         SanityTestCase,
f63228
-        CopyTestCase
f63228
+        CopyTestCase,
f63228
+        CompareDigestTestCase,
f63228
     )
f63228
 
f63228
 if __name__ == "__main__":
f63228
diff --git a/Modules/operator.c b/Modules/operator.c
f63228
--- a/Modules/operator.c
f63228
+++ b/Modules/operator.c
f63228
@@ -235,6 +235,132 @@ op_delslice(PyObject *s, PyObject *a)
f63228
 #define spam2o(OP,ALTOP,DOC) {#OP, op_##OP, METH_O, PyDoc_STR(DOC)}, \
f63228
                            {#ALTOP, op_##OP, METH_O, PyDoc_STR(DOC)},
f63228
 
f63228
+
f63228
+
f63228
+/* compare_digest **********************************************************/
f63228
+
f63228
+/*
f63228
+ * timing safe compare
f63228
+ *
f63228
+ * Returns 1 of the strings are equal.
f63228
+ * In case of len(a) != len(b) the function tries to keep the timing
f63228
+ * dependent on the length of b. CPU cache locally may still alter timing
f63228
+ * a bit.
f63228
+ */
f63228
+static int
f63228
+_tscmp(const unsigned char *a, const unsigned char *b,
f63228
+        Py_ssize_t len_a, Py_ssize_t len_b)
f63228
+{
f63228
+    /* The volatile type declarations make sure that the compiler has no
f63228
+     * chance to optimize and fold the code in any way that may change
f63228
+     * the timing.
f63228
+     */
f63228
+    volatile Py_ssize_t length;
f63228
+    volatile const unsigned char *left;
f63228
+    volatile const unsigned char *right;
f63228
+    Py_ssize_t i;
f63228
+    unsigned char result;
f63228
+
f63228
+    /* loop count depends on length of b */
f63228
+    length = len_b;
f63228
+    left = NULL;
f63228
+    right = b;
f63228
+
f63228
+    /* don't use else here to keep the amount of CPU instructions constant,
f63228
+     * volatile forces re-evaluation
f63228
+     *  */
f63228
+    if (len_a == length) {
f63228
+        left = *((volatile const unsigned char**)&a);
f63228
+        result = 0;
f63228
+    }
f63228
+    if (len_a != length) {
f63228
+        left = b;
f63228
+        result = 1;
f63228
+    }
f63228
+
f63228
+    for (i=0; i < length; i++) {
f63228
+        result |= *left++ ^ *right++;
f63228
+    }
f63228
+
f63228
+    return (result == 0);
f63228
+}
f63228
+
f63228
+PyDoc_STRVAR(compare_digest__doc__,
f63228
+"compare_digest(a, b) -> bool\n"
f63228
+"\n"
f63228
+"Return 'a == b'.  This function uses an approach designed to prevent\n"
f63228
+"timing analysis, making it appropriate for cryptography.\n"
f63228
+"a and b must both be of the same type: either str (ASCII only),\n"
f63228
+"or any type that supports the buffer protocol (e.g. bytes).\n"
f63228
+"\n"
f63228
+"Note: If a and b are of different lengths, or if an error occurs,\n"
f63228
+"a timing attack could theoretically reveal information about the\n"
f63228
+"types and lengths of a and b--but not their values.\n");
f63228
+
f63228
+static PyObject*
f63228
+compare_digest(PyObject *self, PyObject *args)
f63228
+{
f63228
+    PyObject *a, *b;
f63228
+    int rc;
f63228
+
f63228
+    if (!PyArg_ParseTuple(args, "OO:compare_digest", &a, &b)) {
f63228
+        return NULL;
f63228
+    }
f63228
+
f63228
+    /* Unicode string */
f63228
+    if (PyUnicode_Check(a) && PyUnicode_Check(b)) {
f63228
+        rc = _tscmp(PyUnicode_AS_DATA(a),
f63228
+                    PyUnicode_AS_DATA(b),
f63228
+                    PyUnicode_GET_DATA_SIZE(a),
f63228
+                    PyUnicode_GET_DATA_SIZE(b));
f63228
+    }
f63228
+    /* fallback to buffer interface for bytes, bytesarray and other */
f63228
+    else {
f63228
+        Py_buffer view_a;
f63228
+        Py_buffer view_b;
f63228
+
f63228
+        if ((PyObject_CheckBuffer(a) == 0) & (PyObject_CheckBuffer(b) == 0)) {
f63228
+            PyErr_Format(PyExc_TypeError,
f63228
+                         "unsupported operand types(s) or combination of types: "
f63228
+                         "'%.100s' and '%.100s'",
f63228
+                         Py_TYPE(a)->tp_name, Py_TYPE(b)->tp_name);
f63228
+            return NULL;
f63228
+        }
f63228
+
f63228
+        if (PyObject_GetBuffer(a, &view_a, PyBUF_SIMPLE) == -1) {
f63228
+            return NULL;
f63228
+        }
f63228
+        if (view_a.ndim > 1) {
f63228
+            PyErr_SetString(PyExc_BufferError,
f63228
+                            "Buffer must be single dimension");
f63228
+            PyBuffer_Release(&view_a);
f63228
+            return NULL;
f63228
+        }
f63228
+
f63228
+        if (PyObject_GetBuffer(b, &view_b, PyBUF_SIMPLE) == -1) {
f63228
+            PyBuffer_Release(&view_a);
f63228
+            return NULL;
f63228
+        }
f63228
+        if (view_b.ndim > 1) {
f63228
+            PyErr_SetString(PyExc_BufferError,
f63228
+                            "Buffer must be single dimension");
f63228
+            PyBuffer_Release(&view_a);
f63228
+            PyBuffer_Release(&view_b);
f63228
+            return NULL;
f63228
+        }
f63228
+
f63228
+        rc = _tscmp((const unsigned char*)view_a.buf,
f63228
+                    (const unsigned char*)view_b.buf,
f63228
+                    view_a.len,
f63228
+                    view_b.len);
f63228
+
f63228
+        PyBuffer_Release(&view_a);
f63228
+        PyBuffer_Release(&view_b);
f63228
+    }
f63228
+
f63228
+    return PyBool_FromLong(rc);
f63228
+}
f63228
+
f63228
 static struct PyMethodDef operator_methods[] = {
f63228
 
f63228
 spam1o(isCallable,
f63228
@@ -318,6 +444,8 @@ spam2(ne,__ne__, "ne(a, b) -- Same as a!
f63228
 spam2(gt,__gt__, "gt(a, b) -- Same as a>b.")
f63228
 spam2(ge,__ge__, "ge(a, b) -- Same as a>=b.")
f63228
 
f63228
+    {"_compare_digest", (PyCFunction)compare_digest, METH_VARARGS,
f63228
+     compare_digest__doc__},
f63228
     {NULL,              NULL}           /* sentinel */
f63228
 
f63228
 };
f63228