https://github.com/python/cpython/commit/c6465bc55e9411f3d898392746cc65a51b546cf5
commit: c6465bc55e9411f3d898392746cc65a51b546cf5
branch: 3.15
author: Miss Islington (bot) <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-10-10T09:16:53Z
summary:

[3.15] gh-114905: Test that ssl._create_stdlib_context() rejects check_hostname 
with CERT_NONE (GH-155509) (GH-155672)

With PROTOCOL_TLS_CLIENT, which became the default protocol in 3.10, this is
an error.  With an explicitly specified legacy protocol it used to succeed,
silently raising verify_mode to CERT_REQUIRED and ignoring the requested
CERT_NONE.

No caller of ssl._create_stdlib_context() in the standard library passes
check_hostname, so no public API reaches it.
(cherry picked from commit 726e48565670e56affd3e6d461871ec20ee12694)

Co-authored-by: Serhiy Storchaka <[email protected]>

files:
M Lib/ssl.py
M Lib/test/test_ssl.py

diff --git a/Lib/ssl.py b/Lib/ssl.py
index 6929951b414c34..b07281f9758214 100644
--- a/Lib/ssl.py
+++ b/Lib/ssl.py
@@ -769,6 +769,8 @@ def _create_unverified_context(protocol=None, *, 
cert_reqs=CERT_NONE,
         raise ValueError(purpose)
 
     context = SSLContext(protocol)
+    # Setting verify_mode to CERT_NONE fails while check_hostname is
+    # enabled, so assign check_hostname first (gh-114905).
     context.check_hostname = check_hostname
     if cert_reqs is not None:
         context.verify_mode = cert_reqs
diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py
index 77671370e73a2a..f35caf92220f5f 100644
--- a/Lib/test/test_ssl.py
+++ b/Lib/test/test_ssl.py
@@ -1847,6 +1847,35 @@ def test__create_stdlib_context(self):
         self.assertEqual(ctx.verify_mode, ssl.CERT_NONE)
         self._assert_context_options(ctx)
 
+    def test__create_stdlib_context_check_hostname(self):
+        # gh-114905: check_hostname cannot be combined with CERT_NONE,
+        # the default for cert_reqs.
+        msg = "Cannot set verify_mode to CERT_NONE when check_hostname"
+        with self.assertRaisesRegex(ValueError, msg):
+            ssl._create_stdlib_context(check_hostname=True)
+        with self.assertRaisesRegex(ValueError, msg):
+            ssl._create_stdlib_context(cert_reqs=ssl.CERT_NONE,
+                                       check_hostname=True)
+
+        # Accepted before 3.10 with a legacy protocol.
+        if has_tls_protocol('PROTOCOL_TLSv1_2'):
+            with warnings_helper.check_warnings():
+                with self.assertRaisesRegex(ValueError, msg):
+                    ssl._create_stdlib_context(ssl.PROTOCOL_TLSv1_2,
+                                               cert_reqs=ssl.CERT_NONE,
+                                               check_hostname=True)
+
+        # cert_reqs=None leaves PROTOCOL_TLS_CLIENT's CERT_REQUIRED.
+        ctx = ssl._create_stdlib_context(cert_reqs=None, check_hostname=True)
+        self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED)
+        self.assertTrue(ctx.check_hostname)
+
+        # CERT_REQUIRED is covered by test__create_stdlib_context().
+        ctx = ssl._create_stdlib_context(cert_reqs=ssl.CERT_OPTIONAL,
+                                         check_hostname=True)
+        self.assertEqual(ctx.verify_mode, ssl.CERT_OPTIONAL)
+        self.assertTrue(ctx.check_hostname)
+
     def test_delete_sslobject_attributes(self):
         # None of the attributes of _ssl._SSLSocket can be deleted.
         ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to