.. 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
+ Set the allowed ciphers for sockets created with this context when
+ connecting using TLS 1.2 and earlier. The *ciphers* argument should
+ be a string in the `OpenSSL cipher list format
<https://docs.openssl.org/master/man1/ciphers/>`_.
+ To set allowed TLS 1.3 ciphers, use :meth:`SSLContext.set_ciphersuites`.
+
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.
+ When connected, the :meth:`SSLSocket.cipher` method of SSL sockets will
+ 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. 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 details about the negotiated cipher.
- TLS 1.3 cipher suites cannot be disabled with
- :meth:`~SSLContext.set_ciphers`.
+ .. versionadded:: next
.. method:: SSLContext.set_groups(groups)
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 TLS 1.3 ciphers
+ are allowed, the :meth:`SSLContext.set_ciphersuites` method should be
+ called instead of :meth:`SSLContext.set_ciphers`, which only affects
+ ciphers in older TLS versions. The :meth:`SSLContext.get_ciphers` method
+ returns information about ciphers for both TLS 1.3 and earlier versions
+ 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.
(Contributed by Ron Frederick in :gh:`136306`)
+* Added a new method :meth:`ssl.SSLContext.set_ciphersuites` for setting TLS 1.3
+ 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
-------
def test_wrap_socket(sock, *,
cert_reqs=ssl.CERT_NONE, ca_certs=None,
- ciphers=None, certfile=None, keyfile=None,
+ ciphers=None, ciphersuites=None,
+ min_version=None, max_version=None,
+ certfile=None, keyfile=None,
**kwargs):
if not kwargs.get("server_side"):
kwargs["server_hostname"] = SIGNED_CERTFILE_HOSTNAME
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
+ if max_version is not None:
+ context.maximum_version = max_version
return context.wrap_socket(sock, **kwargs)
self.assertRaises(ssl.SSLEOFError, sslobj.read)
+@unittest.skipUnless(has_tls_version('TLSv1_3'), "TLS 1.3 is not available")
+class SimpleBackgroundTestsTLS_1_3(unittest.TestCase):
+ """Tests that connect to a simple server running in the background."""
+
+ def setUp(self):
+ server_ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
+ ciphers = [cipher['name'] for cipher in server_ctx.get_ciphers()
+ if cipher['protocol'] == 'TLSv1.3']
+
+ if not ciphers:
+ self.skipTest("No cipher supports TLSv1.3")
+
+ self.matching_cipher = ciphers[0]
+ # Some tests need at least two ciphers, and are responsible
+ # to skip themselves if matching_cipher == mismatched_cipher.
+ self.mismatched_cipher = ciphers[-1]
+
+ server_ctx.set_ciphersuites(self.matching_cipher)
+ server_ctx.load_cert_chain(SIGNED_CERTFILE)
+ server = ThreadedEchoServer(context=server_ctx)
+ self.enterContext(server)
+ self.server_addr = (HOST, server.port)
+
+ def test_ciphersuites(self):
+ # Test unrecognized TLS 1.3 cipher suite name
+ with (
+ socket.socket(socket.AF_INET) as sock,
+ self.assertRaisesRegex(ssl.SSLError,
+ "No cipher suite can be selected")
+ ):
+ 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)
+
+ def test_ciphersuite_downgrade(self):
+ with test_wrap_socket(socket.socket(socket.AF_INET),
+ cert_reqs=ssl.CERT_NONE,
+ ciphersuites=self.matching_cipher,
+ min_version=ssl.TLSVersion.TLSv1_2,
+ max_version=ssl.TLSVersion.TLSv1_2) as s:
+ s.connect(self.server_addr)
+ self.assertEqual(s.cipher()[1], 'TLSv1.2')
+
+ def test_ciphersuite_mismatch(self):
+ if self.matching_cipher == self.mismatched_cipher:
+ self.skipTest("Multiple TLS 1.3 ciphers are not available")
+
+ 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:
+ self.assertRaises(ssl.SSLError, s.connect, self.server_addr)
+
+
@support.requires_resource('network')
class NetworkedTests(unittest.TestCase):
--- /dev/null
+:class:`~ssl.SSLContext` objects can now set TLS 1.3 cipher suites
+via :meth:`~ssl.SSLContext.set_ciphersuites`.
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]*/
+{
+ if (!SSL_CTX_set_ciphersuites(self->ctx, ciphersuites)) {
+ _setSSLError(get_state_ctx(self), "No cipher suite can be selected.", 0, __FILE__, __LINE__);
+ return NULL;
+ }
+ Py_RETURN_NONE;
+}
+
/*[clinic input]
@critical_section
_ssl._SSLContext.get_ciphers
_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
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"
#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]*/