[Python-checkins] bpo-31431: SSLContext.check_hostname auto-sets CERT_REQUIRED (#3531)

Christian Heimes webhook-mailer at python.org
Fri Sep 15 14:30:00 EDT 2017


https://github.com/python/cpython/commit/e82c034496512139e9ea3f68ceda86c04bc7baab
commit: e82c034496512139e9ea3f68ceda86c04bc7baab
branch: master
author: Christian Heimes <christian at python.org>
committer: GitHub <noreply at github.com>
date: 2017-09-15T20:29:57+02:00
summary:

bpo-31431: SSLContext.check_hostname auto-sets CERT_REQUIRED (#3531)

Signed-off-by: Christian Heimes <christian at python.org>

files:
A Misc/NEWS.d/next/Library/2017-09-13-07-37-20.bpo-31431.dj994R.rst
M Doc/library/ssl.rst
M Lib/test/test_ssl.py
M Modules/_ssl.c

diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst
index eb4d8ace3de..1f3e8d5f781 100644
--- a/Doc/library/ssl.rst
+++ b/Doc/library/ssl.rst
@@ -1674,7 +1674,10 @@ to speed up repeated connections from the same clients.
    :meth:`SSLSocket.do_handshake`. The context's
    :attr:`~SSLContext.verify_mode` must be set to :data:`CERT_OPTIONAL` or
    :data:`CERT_REQUIRED`, and you must pass *server_hostname* to
-   :meth:`~SSLContext.wrap_socket` in order to match the hostname.
+   :meth:`~SSLContext.wrap_socket` in order to match the hostname.  Enabling
+   hostname checking automatically sets :attr:`~SSLContext.verify_mode` from
+   :data:`CERT_NONE` to :data:`CERT_REQUIRED`.  It cannot be set back to
+   :data:`CERT_NONE` as long as hostname checking is enabled.
 
    Example::
 
@@ -1691,6 +1694,13 @@ to speed up repeated connections from the same clients.
 
    .. versionadded:: 3.4
 
+   .. versionchanged:: 3.7
+
+      :attr:`~SSLContext.verify_mode` is now automatically changed
+      to :data:`CERT_REQUIRED`  when hostname checking is enabled and
+      :attr:`~SSLContext.verify_mode` is :data:`CERT_NONE`. Previously
+      the same operation would have failed with a :exc:`ValueError`.
+
    .. note::
 
      This features requires OpenSSL 0.9.8f or newer.
diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py
index 2978b8b3ebd..aa2429ac982 100644
--- a/Lib/test/test_ssl.py
+++ b/Lib/test/test_ssl.py
@@ -1363,24 +1363,45 @@ def test__create_stdlib_context(self):
     def test_check_hostname(self):
         ctx = ssl.SSLContext(ssl.PROTOCOL_TLS)
         self.assertFalse(ctx.check_hostname)
+        self.assertEqual(ctx.verify_mode, ssl.CERT_NONE)
 
-        # Requires CERT_REQUIRED or CERT_OPTIONAL
-        with self.assertRaises(ValueError):
-            ctx.check_hostname = True
+        # Auto set CERT_REQUIRED
+        ctx.check_hostname = True
+        self.assertTrue(ctx.check_hostname)
+        self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED)
+        ctx.check_hostname = False
         ctx.verify_mode = ssl.CERT_REQUIRED
         self.assertFalse(ctx.check_hostname)
+        self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED)
+
+        # Changing verify_mode does not affect check_hostname
+        ctx.check_hostname = False
+        ctx.verify_mode = ssl.CERT_NONE
+        ctx.check_hostname = False
+        self.assertFalse(ctx.check_hostname)
+        self.assertEqual(ctx.verify_mode, ssl.CERT_NONE)
+        # Auto set
         ctx.check_hostname = True
         self.assertTrue(ctx.check_hostname)
+        self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED)
 
+        ctx.check_hostname = False
         ctx.verify_mode = ssl.CERT_OPTIONAL
+        ctx.check_hostname = False
+        self.assertFalse(ctx.check_hostname)
+        self.assertEqual(ctx.verify_mode, ssl.CERT_OPTIONAL)
+        # keep CERT_OPTIONAL
         ctx.check_hostname = True
         self.assertTrue(ctx.check_hostname)
+        self.assertEqual(ctx.verify_mode, ssl.CERT_OPTIONAL)
 
         # Cannot set CERT_NONE with check_hostname enabled
         with self.assertRaises(ValueError):
             ctx.verify_mode = ssl.CERT_NONE
         ctx.check_hostname = False
         self.assertFalse(ctx.check_hostname)
+        ctx.verify_mode = ssl.CERT_NONE
+        self.assertEqual(ctx.verify_mode, ssl.CERT_NONE)
 
     def test_context_client_server(self):
         # PROTOCOL_TLS_CLIENT has sane defaults
diff --git a/Misc/NEWS.d/next/Library/2017-09-13-07-37-20.bpo-31431.dj994R.rst b/Misc/NEWS.d/next/Library/2017-09-13-07-37-20.bpo-31431.dj994R.rst
new file mode 100644
index 00000000000..6083fd65bd1
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2017-09-13-07-37-20.bpo-31431.dj994R.rst
@@ -0,0 +1,2 @@
+SSLContext.check_hostname now automatically sets SSLContext.verify_mode to
+ssl.CERT_REQUIRED instead of failing with a ValueError.
diff --git a/Modules/_ssl.c b/Modules/_ssl.c
index 8aaaa3212c1..73abad3dcf1 100644
--- a/Modules/_ssl.c
+++ b/Modules/_ssl.c
@@ -3227,10 +3227,10 @@ set_check_hostname(PySSLContext *self, PyObject *arg, void *c)
         return -1;
     if (check_hostname &&
             SSL_CTX_get_verify_mode(self->ctx) == SSL_VERIFY_NONE) {
-        PyErr_SetString(PyExc_ValueError,
-                        "check_hostname needs a SSL context with either "
-                        "CERT_OPTIONAL or CERT_REQUIRED");
-        return -1;
+        /* check_hostname = True sets verify_mode = CERT_REQUIRED */
+        if (_set_verify_mode(self->ctx, PY_SSL_CERT_REQUIRED) == -1) {
+            return -1;
+        }
     }
     self->check_hostname = check_hostname;
     return 0;



More information about the Python-checkins mailing list