From 66978c5ca502e40da6891a4a9483a96759a9a31a Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 10 Aug 2026 19:31:48 +0300 Subject: [PATCH] gh-114905: Test that ssl._create_stdlib_context() rejects check_hostname with CERT_NONE 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. --- Lib/ssl.py | 2 ++ Lib/test/test_ssl.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/Lib/ssl.py b/Lib/ssl.py index 3c0361330d7e951..2f4720e3065fdba 100644 --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -754,6 +754,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 6446f96eab42a43..2bba665d19343e6 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -1817,6 +1817,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_check_hostname(self): with warnings_helper.check_warnings(): ctx = ssl.SSLContext(ssl.PROTOCOL_TLS)