From 5a1cc22eb6a3c4fef84c76330542316accf03e3d Mon Sep 17 00:00:00 2001 From: Ron Frederick Date: Mon, 28 Jul 2025 21:58:42 -0700 Subject: [PATCH 1/7] gh-137197: Add SSLContext.set_ciphersuites to set TLS 1.3 ciphers --- Doc/library/ssl.rst | 36 +++++++++++++++++++++++++----------- Doc/whatsnew/3.15.rst | 5 +++++ Lib/test/test_ssl.py | 33 +++++++++++++++++++++++++++++---- Modules/_ssl.c | 28 ++++++++++++++++++++++------ Modules/clinic/_ssl.c.h | 41 ++++++++++++++++++++++++++++++++++++++++- 5 files changed, 121 insertions(+), 22 deletions(-) diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index ff6053cb7e94d9..58eb9ac64c5aa8 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -1684,19 +1684,29 @@ to speed up repeated connections from the same clients. .. method:: SSLContext.set_ciphers(ciphers) - Set the available ciphers for sockets created with this context. - It should be a string in the `OpenSSL cipher list format - `_. - If no cipher can be selected (because compile-time options or other + Set the allowed ciphers for sockets created with this context when + connecting using TLS 1.2 and earlier. It should be a string in the `OpenSSL + cipher list format `_. + To set allowed TLS 1.3 ciphers, use :meth:`SSHContext.set_ciphersuites`. + below. If no cipher can be selected (because compile-time options or other configuration forbids use of all the specified ciphers), an :class:`SSLError` will be raised. .. note:: when connected, the :meth:`SSLSocket.cipher` method of SSL sockets will - give the currently selected cipher. + return the negotiated cipher and associated TLS version. - TLS 1.3 cipher suites cannot be disabled with - :meth:`~SSLContext.set_ciphers`. +.. method:: SSLContext.set_ciphersuites(ciphersuites) + + Set the allowed ciphers for sockets created with this context when + connecting using TLS 1.3. It should be a colon-separate string of TLS 1.3 + cipher names. If no cipher can be selected (because compile-time options + or other configuration forbids use of all the specified ciphers), an + :class:`SSLError` will be raised. + + .. note:: + when connected, the :meth:`SSLSocket.cipher` method of SSL sockets will + return the negotiated cipher and associated TLS version. .. method:: SSLContext.set_groups(groups) @@ -2844,10 +2854,14 @@ TLS 1.3 The TLS 1.3 protocol behaves slightly differently than previous version of TLS/SSL. Some new TLS 1.3 features are not yet available. -- TLS 1.3 uses a disjunct set of cipher suites. All AES-GCM and - ChaCha20 cipher suites are enabled by default. The method - :meth:`SSLContext.set_ciphers` cannot enable or disable any TLS 1.3 - ciphers yet, but :meth:`SSLContext.get_ciphers` returns them. +- TLS 1.3 uses a disjunct set of cipher suites. All AES-GCM and ChaCha20 + cipher suites are enabled by default. To restrict which TLS1.3 ciphers + are allowed, the method :meth:`SSLContext.set_ciphersuites` should be + called instead of :meth:`SSLContext.set_ciphers`, which only affects + ciphers in older TLS versions. The method :meth:`SSLContext.get_ciphers` + returns information about ciphers for both TLS 1.3 and earlier versions + and the method :meth:`SSLSocket.cipher` returns the negotiated cipher and + the associated TLS version once a connection is established. - Session tickets are no longer sent as part of the initial handshake and are handled differently. :attr:`SSLSocket.session` and :class:`SSLSession` are not compatible with TLS 1.3. diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 9f01b52f1aff3b..0aec043364942a 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -332,6 +332,11 @@ ssl (Contributed by Ron Frederick in :gh:`136306`) +* Added new method :meth:`ssl.SSLContext.set_ciphersuites` for setting TLS 1.3 + ciphers and updated the documentation on :meth:`ssl.SSLContext.set_ciphers` + to mention that it only applies to TLS 1.2 and earlier and that this new + method must be used to set TLS 1.3 cipher suites. + (Contributed by Ron Frederick in :gh:`137197`) tarfile ------- diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index b5263129baed3f..c8457e8f75a876 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -261,10 +261,9 @@ def utc_offset(): #NOTE: ignore issues like #1647654 ) -def test_wrap_socket(sock, *, - cert_reqs=ssl.CERT_NONE, ca_certs=None, - ciphers=None, certfile=None, keyfile=None, - **kwargs): +def test_wrap_socket(sock, *, cert_reqs=ssl.CERT_NONE, ca_certs=None, + ciphers=None, ciphersuites=None, min_version=None, + certfile=None, keyfile=None, **kwargs): if not kwargs.get("server_side"): kwargs["server_hostname"] = SIGNED_CERTFILE_HOSTNAME context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) @@ -280,6 +279,10 @@ def test_wrap_socket(sock, *, context.load_cert_chain(certfile, keyfile) if ciphers is not None: context.set_ciphers(ciphers) + if ciphersuites is not None: + context.set_ciphersuites(ciphersuites) + if min_version is not None: + context.minimum_version = min_version return context.wrap_socket(sock, **kwargs) @@ -2109,6 +2112,28 @@ def test_ciphers(self): cert_reqs=ssl.CERT_NONE, ciphers="^$:,;?*'dorothyx") s.connect(self.server_addr) + def test_ciphersuites(self): + with test_wrap_socket(socket.socket(socket.AF_INET), + cert_reqs=ssl.CERT_NONE, + min_version=ssl.TLSVersion.TLSv1_3) as s: + s.connect(self.server_addr) + self.assertEqual(s.cipher()[1], "TLSv1.3") + with test_wrap_socket(socket.socket(socket.AF_INET), + cert_reqs=ssl.CERT_NONE, + ciphersuites="TLS_AES_256_GCM_SHA384", + min_version=ssl.TLSVersion.TLSv1_3) as s: + s.connect(self.server_addr) + self.assertEqual(s.cipher(), + ("TLS_AES_256_GCM_SHA384", "TLSv1.3", 256)) + # Error checking can happen at instantiation or when connecting + with self.assertRaisesRegex(ssl.SSLError, + "No cipher suite can be selected"): + with socket.socket(socket.AF_INET) as sock: + s = test_wrap_socket(sock, cert_reqs=ssl.CERT_NONE, + ciphersuites="XXX", + min_version=ssl.TLSVersion.TLSv1_3) + s.connect(self.server_addr) + def test_get_ca_certs_capath(self): # capath certs are loaded on request ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index ab30258faf3f62..348fa3e054eabf 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -3595,12 +3595,27 @@ _ssl__SSLContext_set_ciphers_impl(PySSLContext *self, const char *cipherlist) { int ret = SSL_CTX_set_cipher_list(self->ctx, cipherlist); if (ret == 0) { - /* Clearing the error queue is necessary on some OpenSSL versions, - otherwise the error will be reported again when another SSL call - is done. */ - ERR_clear_error(); - PyErr_SetString(get_state_ctx(self)->PySSLErrorObject, - "No cipher can be selected."); + _setSSLError(get_state_ctx(self), "No cipher can be selected.", 0, __FILE__, __LINE__); + return NULL; + } + Py_RETURN_NONE; +} + +/*[clinic input] +@critical_section +_ssl._SSLContext.set_ciphersuites + ciphersuites: str + / +[clinic start generated code]*/ + +static PyObject * +_ssl__SSLContext_set_ciphersuites_impl(PySSLContext *self, + const char *ciphersuites) +/*[clinic end generated code: output=9915bec58e54d76d input=2afcc3693392be41]*/ +{ + int ret = SSL_CTX_set_ciphersuites(self->ctx, ciphersuites); + if (ret == 0) { + _setSSLError(get_state_ctx(self), "No cipher suite can be selected.", 0, __FILE__, __LINE__); return NULL; } Py_RETURN_NONE; @@ -5583,6 +5598,7 @@ static struct PyMethodDef context_methods[] = { _SSL__SSLCONTEXT__WRAP_SOCKET_METHODDEF _SSL__SSLCONTEXT__WRAP_BIO_METHODDEF _SSL__SSLCONTEXT_SET_CIPHERS_METHODDEF + _SSL__SSLCONTEXT_SET_CIPHERSUITES_METHODDEF _SSL__SSLCONTEXT_SET_GROUPS_METHODDEF _SSL__SSLCONTEXT__SET_ALPN_PROTOCOLS_METHODDEF _SSL__SSLCONTEXT_LOAD_CERT_CHAIN_METHODDEF diff --git a/Modules/clinic/_ssl.c.h b/Modules/clinic/_ssl.c.h index 5b80fab0abb45e..e8b51c1f1e326d 100644 --- a/Modules/clinic/_ssl.c.h +++ b/Modules/clinic/_ssl.c.h @@ -969,6 +969,45 @@ _ssl__SSLContext_set_ciphers(PyObject *self, PyObject *arg) return return_value; } +PyDoc_STRVAR(_ssl__SSLContext_set_ciphersuites__doc__, +"set_ciphersuites($self, ciphersuites, /)\n" +"--\n" +"\n"); + +#define _SSL__SSLCONTEXT_SET_CIPHERSUITES_METHODDEF \ + {"set_ciphersuites", (PyCFunction)_ssl__SSLContext_set_ciphersuites, METH_O, _ssl__SSLContext_set_ciphersuites__doc__}, + +static PyObject * +_ssl__SSLContext_set_ciphersuites_impl(PySSLContext *self, + const char *ciphersuites); + +static PyObject * +_ssl__SSLContext_set_ciphersuites(PyObject *self, PyObject *arg) +{ + PyObject *return_value = NULL; + const char *ciphersuites; + + if (!PyUnicode_Check(arg)) { + _PyArg_BadArgument("set_ciphersuites", "argument", "str", arg); + goto exit; + } + Py_ssize_t ciphersuites_length; + ciphersuites = PyUnicode_AsUTF8AndSize(arg, &ciphersuites_length); + if (ciphersuites == NULL) { + goto exit; + } + if (strlen(ciphersuites) != (size_t)ciphersuites_length) { + PyErr_SetString(PyExc_ValueError, "embedded null character"); + goto exit; + } + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _ssl__SSLContext_set_ciphersuites_impl((PySSLContext *)self, ciphersuites); + Py_END_CRITICAL_SECTION(); + +exit: + return return_value; +} + PyDoc_STRVAR(_ssl__SSLContext_get_ciphers__doc__, "get_ciphers($self, /)\n" "--\n" @@ -3142,4 +3181,4 @@ _ssl_enum_crls(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObje #ifndef _SSL_ENUM_CRLS_METHODDEF #define _SSL_ENUM_CRLS_METHODDEF #endif /* !defined(_SSL_ENUM_CRLS_METHODDEF) */ -/*[clinic end generated code: output=c409bdf3c123b28b input=a9049054013a1b77]*/ +/*[clinic end generated code: output=4e35d2ea2fc46023 input=a9049054013a1b77]*/ From 09534fdb552fe8f6641607903578a417d030c3f1 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Tue, 29 Jul 2025 05:12:54 +0000 Subject: [PATCH 2/7] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2025-07-29-05-12-50.gh-issue-137197.bMK3sO.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2025-07-29-05-12-50.gh-issue-137197.bMK3sO.rst diff --git a/Misc/NEWS.d/next/Library/2025-07-29-05-12-50.gh-issue-137197.bMK3sO.rst b/Misc/NEWS.d/next/Library/2025-07-29-05-12-50.gh-issue-137197.bMK3sO.rst new file mode 100644 index 00000000000000..592a1f6914fc82 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-07-29-05-12-50.gh-issue-137197.bMK3sO.rst @@ -0,0 +1 @@ +:mod:`ssl` can now set TLS 1.3 cipher suites. From 6783fe6ce3e35c533cbee9e25d39d9535ff675b7 Mon Sep 17 00:00:00 2001 From: Ron Frederick Date: Mon, 28 Jul 2025 22:57:20 -0700 Subject: [PATCH 3/7] gh-137197: Fix typo in documentation --- Doc/library/ssl.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index 58eb9ac64c5aa8..7a21f52ecdbc08 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -1687,7 +1687,7 @@ to speed up repeated connections from the same clients. Set the allowed ciphers for sockets created with this context when connecting using TLS 1.2 and earlier. It should be a string in the `OpenSSL cipher list format `_. - To set allowed TLS 1.3 ciphers, use :meth:`SSHContext.set_ciphersuites`. + To set allowed TLS 1.3 ciphers, use :meth:`SSLContext.set_ciphersuites`. below. If no cipher can be selected (because compile-time options or other configuration forbids use of all the specified ciphers), an :class:`SSLError` will be raised. From d3375f1b541054531d491ca27d208413427c5742 Mon Sep 17 00:00:00 2001 From: Ron Frederick Date: Fri, 8 Aug 2025 17:20:47 -0700 Subject: [PATCH 4/7] gh-137197: Update what's new text Clarify when to use the original set_ciphers (TLS 1.2 and earlier) vs. the new set_ciphersuites (TLS 1.3) methods and that both can be used at once. --- Doc/whatsnew/3.15.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 0aec043364942a..d0ffbe090da88d 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -333,11 +333,13 @@ ssl (Contributed by Ron Frederick in :gh:`136306`) * Added new method :meth:`ssl.SSLContext.set_ciphersuites` for setting TLS 1.3 - ciphers and updated the documentation on :meth:`ssl.SSLContext.set_ciphers` - to mention that it only applies to TLS 1.2 and earlier and that this new - method must be used to set TLS 1.3 cipher suites. + ciphers. For TLS 1.2 or earlier, :meth:`ssl.SSLContext.set_ciphers` should + continue to be used. Both calls can be made on the same context and the + selected cipher suite will depend on the TLS version negotiated when a + connection is made. (Contributed by Ron Frederick in :gh:`137197`) + tarfile ------- From 48e5164e8487c42edb4f770c536d2efd1cb97a34 Mon Sep 17 00:00:00 2001 From: Ron Frederick Date: Sat, 9 Aug 2025 09:16:49 -0700 Subject: [PATCH 5/7] gh-137197: Address review comments --- Doc/library/ssl.rst | 24 +++++++++++++----------- Lib/test/test_ssl.py | 29 ++++++++++++++++++++--------- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index 7a21f52ecdbc08..057108e6778201 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -1685,28 +1685,29 @@ to speed up repeated connections from the same clients. .. method:: SSLContext.set_ciphers(ciphers) Set the allowed ciphers for sockets created with this context when - connecting using TLS 1.2 and earlier. It should be a string in the `OpenSSL - cipher list format `_. + connecting using TLS 1.2 and earlier. The *ciphers* argument should + be a string in the `OpenSSL cipher list format + `_. To set allowed TLS 1.3 ciphers, use :meth:`SSLContext.set_ciphersuites`. - below. If no cipher can be selected (because compile-time options or other + If no cipher can be selected (because compile-time options or other configuration forbids use of all the specified ciphers), an :class:`SSLError` will be raised. .. note:: when connected, the :meth:`SSLSocket.cipher` method of SSL sockets will - return the negotiated cipher and associated TLS version. + return details about the negotiated cipher. .. method:: SSLContext.set_ciphersuites(ciphersuites) Set the allowed ciphers for sockets created with this context when - connecting using TLS 1.3. It should be a colon-separate string of TLS 1.3 - cipher names. If no cipher can be selected (because compile-time options - or other configuration forbids use of all the specified ciphers), an - :class:`SSLError` will be raised. + connecting using TLS 1.3. The *ciphersuites* argument should be a + colon-separate string of TLS 1.3 cipher names. If no cipher can be + selected (because compile-time options or other configuration forbids + use of all the specified ciphers), an :class:`SSLError` will be raised. .. note:: when connected, the :meth:`SSLSocket.cipher` method of SSL sockets will - return the negotiated cipher and associated TLS version. + return details about the negotiated cipher. .. method:: SSLContext.set_groups(groups) @@ -2860,8 +2861,9 @@ of TLS/SSL. Some new TLS 1.3 features are not yet available. called instead of :meth:`SSLContext.set_ciphers`, which only affects ciphers in older TLS versions. The method :meth:`SSLContext.get_ciphers` returns information about ciphers for both TLS 1.3 and earlier versions - and the method :meth:`SSLSocket.cipher` returns the negotiated cipher and - the associated TLS version once a connection is established. + and the method :meth:`SSLSocket.cipher` returns information about the + negotiated cipher for both TLS 1.3 and earlier versions once a connection + is established. - Session tickets are no longer sent as part of the initial handshake and are handled differently. :attr:`SSLSocket.session` and :class:`SSLSession` are not compatible with TLS 1.3. diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index c8457e8f75a876..f969e5c2e039e3 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -261,9 +261,11 @@ def utc_offset(): #NOTE: ignore issues like #1647654 ) -def test_wrap_socket(sock, *, cert_reqs=ssl.CERT_NONE, ca_certs=None, +def test_wrap_socket(sock, *, + cert_reqs=ssl.CERT_NONE, ca_certs=None, ciphers=None, ciphersuites=None, min_version=None, - certfile=None, keyfile=None, **kwargs): + certfile=None, keyfile=None, + **kwargs): if not kwargs.get("server_side"): kwargs["server_hostname"] = SIGNED_CERTFILE_HOSTNAME context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) @@ -1866,6 +1868,10 @@ class SimpleBackgroundTests(unittest.TestCase): def setUp(self): self.server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + + if has_tls_version('TLSv1_3'): + self.server_context.set_ciphersuites('TLS_AES_256_GCM_SHA384') + self.server_context.load_cert_chain(SIGNED_CERTFILE) server = ThreadedEchoServer(context=self.server_context) self.enterContext(server) @@ -2112,12 +2118,9 @@ def test_ciphers(self): cert_reqs=ssl.CERT_NONE, ciphers="^$:,;?*'dorothyx") s.connect(self.server_addr) + @requires_tls_version('TLSv1_3') def test_ciphersuites(self): - with test_wrap_socket(socket.socket(socket.AF_INET), - cert_reqs=ssl.CERT_NONE, - min_version=ssl.TLSVersion.TLSv1_3) as s: - s.connect(self.server_addr) - self.assertEqual(s.cipher()[1], "TLSv1.3") + # Test successful TLS 1.3 handshake with test_wrap_socket(socket.socket(socket.AF_INET), cert_reqs=ssl.CERT_NONE, ciphersuites="TLS_AES_256_GCM_SHA384", @@ -2125,14 +2128,22 @@ def test_ciphersuites(self): s.connect(self.server_addr) self.assertEqual(s.cipher(), ("TLS_AES_256_GCM_SHA384", "TLSv1.3", 256)) - # Error checking can happen at instantiation or when connecting + + # Test mismatched TLS 1.3 cipher suites + with test_wrap_socket(socket.socket(socket.AF_INET), + cert_reqs=ssl.CERT_NONE, + ciphersuites="TLS_AES_128_GCM_SHA256", + min_version=ssl.TLSVersion.TLSv1_3) as s: + with self.assertRaises(ssl.SSLError): + s.connect(self.server_addr) + + # Test unrecognized TLS 1.3 cipher suite name with self.assertRaisesRegex(ssl.SSLError, "No cipher suite can be selected"): with socket.socket(socket.AF_INET) as sock: s = test_wrap_socket(sock, cert_reqs=ssl.CERT_NONE, ciphersuites="XXX", min_version=ssl.TLSVersion.TLSv1_3) - s.connect(self.server_addr) def test_get_ca_certs_capath(self): # capath certs are loaded on request From 11b760e0a93229943ad717597502e4af6cead918 Mon Sep 17 00:00:00 2001 From: Ron Frederick Date: Sat, 9 Aug 2025 13:23:56 -0700 Subject: [PATCH 6/7] gh-137197: Rework test cases This commit reworks the set_ciphersuites() test cases, moving them into their own class to avoid any changes to existing tests. It also makes the cipher selection dynamic to avoid potentially trying to use a cipher not available in some environments. --- Lib/test/test_ssl.py | 78 ++++++++++++++++++++++++++------------------ 1 file changed, 47 insertions(+), 31 deletions(-) diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index f969e5c2e039e3..ea45afc8eff8a0 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -1868,10 +1868,6 @@ class SimpleBackgroundTests(unittest.TestCase): def setUp(self): self.server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) - - if has_tls_version('TLSv1_3'): - self.server_context.set_ciphersuites('TLS_AES_256_GCM_SHA384') - self.server_context.load_cert_chain(SIGNED_CERTFILE) server = ThreadedEchoServer(context=self.server_context) self.enterContext(server) @@ -2118,33 +2114,6 @@ def test_ciphers(self): cert_reqs=ssl.CERT_NONE, ciphers="^$:,;?*'dorothyx") s.connect(self.server_addr) - @requires_tls_version('TLSv1_3') - def test_ciphersuites(self): - # Test successful TLS 1.3 handshake - with test_wrap_socket(socket.socket(socket.AF_INET), - cert_reqs=ssl.CERT_NONE, - ciphersuites="TLS_AES_256_GCM_SHA384", - min_version=ssl.TLSVersion.TLSv1_3) as s: - s.connect(self.server_addr) - self.assertEqual(s.cipher(), - ("TLS_AES_256_GCM_SHA384", "TLSv1.3", 256)) - - # Test mismatched TLS 1.3 cipher suites - with test_wrap_socket(socket.socket(socket.AF_INET), - cert_reqs=ssl.CERT_NONE, - ciphersuites="TLS_AES_128_GCM_SHA256", - min_version=ssl.TLSVersion.TLSv1_3) as s: - with self.assertRaises(ssl.SSLError): - s.connect(self.server_addr) - - # Test unrecognized TLS 1.3 cipher suite name - with self.assertRaisesRegex(ssl.SSLError, - "No cipher suite can be selected"): - with socket.socket(socket.AF_INET) as sock: - s = test_wrap_socket(sock, cert_reqs=ssl.CERT_NONE, - ciphersuites="XXX", - min_version=ssl.TLSVersion.TLSv1_3) - def test_get_ca_certs_capath(self): # capath certs are loaded on request ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) @@ -2274,6 +2243,53 @@ def test_transport_eof(self): self.assertRaises(ssl.SSLEOFError, sslobj.read) +@requires_tls_version('TLSv1_3') +class SimpleBackgroundTestsTLS_1_3(unittest.TestCase): + """Tests that connect to a simple server running in the background""" + + def setUp(self): + ciphers = [cipher['name'] for cipher in ctx.get_ciphers() + if cipher['protocol'] == 'TLSv1.3'] + + self.matching_cipher = ciphers[0] + self.mismatched_cipher = ciphers[-1] + + self.server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + self.server_context.set_ciphersuites(self.matching_cipher) + self.server_context.load_cert_chain(SIGNED_CERTFILE) + server = ThreadedEchoServer(context=self.server_context) + self.enterContext(server) + self.server_addr = (HOST, server.port) + + def test_ciphersuites(self): + # Test unrecognized TLS 1.3 cipher suite name + with self.assertRaisesRegex(ssl.SSLError, + "No cipher suite can be selected"): + with socket.socket(socket.AF_INET) as sock: + s = test_wrap_socket(sock, cert_reqs=ssl.CERT_NONE, + ciphersuites="XXX", + min_version=ssl.TLSVersion.TLSv1_3) + + # Test successful TLS 1.3 handshake + with test_wrap_socket(socket.socket(socket.AF_INET), + cert_reqs=ssl.CERT_NONE, + ciphersuites=self.matching_cipher, + min_version=ssl.TLSVersion.TLSv1_3) as s: + s.connect(self.server_addr) + self.assertEqual(s.cipher()[0], self.matching_cipher) + + # Test mismatched TLS 1.3 cipher suites + if self.matching_client != self.mismatched_cipher: + with test_wrap_socket(socket.socket(socket.AF_INET), + cert_reqs=ssl.CERT_NONE, + ciphersuites=self.mismatched_cipher, + min_version=ssl.TLSVersion.TLSv1_3) as s: + with self.assertRaises(ssl.SSLError): + s.connect(self.server_addr) + else: + self.skipTest("Multiple TLS 1.3 ciphers are not available") + + @support.requires_resource('network') class NetworkedTests(unittest.TestCase): From eaae5758a659f91499f613faad5f13348403ee09 Mon Sep 17 00:00:00 2001 From: Ron Frederick Date: Wed, 13 Aug 2025 17:08:11 -0700 Subject: [PATCH 7/7] gh-137197: Fix capitalization in docs --- Doc/library/ssl.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index 057108e6778201..806db601cc3cc6 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -1694,7 +1694,7 @@ to speed up repeated connections from the same clients. :class:`SSLError` will be raised. .. note:: - when connected, the :meth:`SSLSocket.cipher` method of SSL sockets will + When connected, the :meth:`SSLSocket.cipher` method of SSL sockets will return details about the negotiated cipher. .. method:: SSLContext.set_ciphersuites(ciphersuites) @@ -1706,7 +1706,7 @@ to speed up repeated connections from the same clients. use of all the specified ciphers), an :class:`SSLError` will be raised. .. note:: - when connected, the :meth:`SSLSocket.cipher` method of SSL sockets will + When connected, the :meth:`SSLSocket.cipher` method of SSL sockets will return details about the negotiated cipher. .. method:: SSLContext.set_groups(groups)