mirror of
https://github.com/python/cpython.git
synced 2025-12-03 08:04:34 +00:00
gh-137197: Add SSLContext.set_ciphersuites to set TLSv1.3 ciphers (#137198)
Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
This commit is contained in:
parent
11217a883d
commit
bacb7771fb
6 changed files with 169 additions and 12 deletions
|
|
@ -1684,19 +1684,33 @@ to speed up repeated connections from the same clients.
|
||||||
|
|
||||||
.. method:: SSLContext.set_ciphers(ciphers)
|
.. method:: SSLContext.set_ciphers(ciphers)
|
||||||
|
|
||||||
Set the available ciphers for sockets created with this context.
|
Set the allowed ciphers for sockets created with this context when
|
||||||
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
|
||||||
<https://docs.openssl.org/master/man1/ciphers/>`_.
|
<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
|
If no cipher can be selected (because compile-time options or other
|
||||||
configuration forbids use of all the specified ciphers), an
|
configuration forbids use of all the specified ciphers), an
|
||||||
:class:`SSLError` will be raised.
|
:class:`SSLError` will be raised.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
when connected, the :meth:`SSLSocket.cipher` method of SSL sockets will
|
When connected, the :meth:`SSLSocket.cipher` method of SSL sockets will
|
||||||
give the currently selected cipher.
|
return details about the negotiated cipher.
|
||||||
|
|
||||||
TLS 1.3 cipher suites cannot be disabled with
|
.. method:: SSLContext.set_ciphersuites(ciphersuites)
|
||||||
:meth:`~SSLContext.set_ciphers`.
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
.. versionadded:: next
|
||||||
|
|
||||||
.. method:: SSLContext.set_groups(groups)
|
.. method:: SSLContext.set_groups(groups)
|
||||||
|
|
||||||
|
|
@ -2845,10 +2859,15 @@ TLS 1.3
|
||||||
The TLS 1.3 protocol behaves slightly differently than previous version
|
The TLS 1.3 protocol behaves slightly differently than previous version
|
||||||
of TLS/SSL. Some new TLS 1.3 features are not yet available.
|
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
|
- TLS 1.3 uses a disjunct set of cipher suites. All AES-GCM and ChaCha20
|
||||||
ChaCha20 cipher suites are enabled by default. The method
|
cipher suites are enabled by default. To restrict which TLS 1.3 ciphers
|
||||||
:meth:`SSLContext.set_ciphers` cannot enable or disable any TLS 1.3
|
are allowed, the :meth:`SSLContext.set_ciphersuites` method should be
|
||||||
ciphers yet, but :meth:`SSLContext.get_ciphers` returns them.
|
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
|
- Session tickets are no longer sent as part of the initial handshake and
|
||||||
are handled differently. :attr:`SSLSocket.session` and :class:`SSLSession`
|
are handled differently. :attr:`SSLSocket.session` and :class:`SSLSession`
|
||||||
are not compatible with TLS 1.3.
|
are not compatible with TLS 1.3.
|
||||||
|
|
|
||||||
|
|
@ -426,6 +426,13 @@ ssl
|
||||||
|
|
||||||
(Contributed by Ron Frederick in :gh:`136306`)
|
(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
|
tarfile
|
||||||
-------
|
-------
|
||||||
|
|
|
||||||
|
|
@ -263,7 +263,9 @@ ignore_deprecation = warnings_helper.ignore_warnings(
|
||||||
|
|
||||||
def test_wrap_socket(sock, *,
|
def test_wrap_socket(sock, *,
|
||||||
cert_reqs=ssl.CERT_NONE, ca_certs=None,
|
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):
|
**kwargs):
|
||||||
if not kwargs.get("server_side"):
|
if not kwargs.get("server_side"):
|
||||||
kwargs["server_hostname"] = SIGNED_CERTFILE_HOSTNAME
|
kwargs["server_hostname"] = SIGNED_CERTFILE_HOSTNAME
|
||||||
|
|
@ -280,6 +282,12 @@ def test_wrap_socket(sock, *,
|
||||||
context.load_cert_chain(certfile, keyfile)
|
context.load_cert_chain(certfile, keyfile)
|
||||||
if ciphers is not None:
|
if ciphers is not None:
|
||||||
context.set_ciphers(ciphers)
|
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)
|
return context.wrap_socket(sock, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2238,6 +2246,68 @@ class SimpleBackgroundTests(unittest.TestCase):
|
||||||
self.assertRaises(ssl.SSLEOFError, sslobj.read)
|
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')
|
@support.requires_resource('network')
|
||||||
class NetworkedTests(unittest.TestCase):
|
class NetworkedTests(unittest.TestCase):
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
:class:`~ssl.SSLContext` objects can now set TLS 1.3 cipher suites
|
||||||
|
via :meth:`~ssl.SSLContext.set_ciphersuites`.
|
||||||
|
|
@ -3614,6 +3614,25 @@ _ssl__SSLContext_set_ciphers_impl(PySSLContext *self, const char *cipherlist)
|
||||||
Py_RETURN_NONE;
|
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]
|
/*[clinic input]
|
||||||
@critical_section
|
@critical_section
|
||||||
_ssl._SSLContext.get_ciphers
|
_ssl._SSLContext.get_ciphers
|
||||||
|
|
@ -5595,6 +5614,7 @@ static struct PyMethodDef context_methods[] = {
|
||||||
_SSL__SSLCONTEXT__WRAP_SOCKET_METHODDEF
|
_SSL__SSLCONTEXT__WRAP_SOCKET_METHODDEF
|
||||||
_SSL__SSLCONTEXT__WRAP_BIO_METHODDEF
|
_SSL__SSLCONTEXT__WRAP_BIO_METHODDEF
|
||||||
_SSL__SSLCONTEXT_SET_CIPHERS_METHODDEF
|
_SSL__SSLCONTEXT_SET_CIPHERS_METHODDEF
|
||||||
|
_SSL__SSLCONTEXT_SET_CIPHERSUITES_METHODDEF
|
||||||
_SSL__SSLCONTEXT_SET_GROUPS_METHODDEF
|
_SSL__SSLCONTEXT_SET_GROUPS_METHODDEF
|
||||||
_SSL__SSLCONTEXT__SET_ALPN_PROTOCOLS_METHODDEF
|
_SSL__SSLCONTEXT__SET_ALPN_PROTOCOLS_METHODDEF
|
||||||
_SSL__SSLCONTEXT_LOAD_CERT_CHAIN_METHODDEF
|
_SSL__SSLCONTEXT_LOAD_CERT_CHAIN_METHODDEF
|
||||||
|
|
|
||||||
41
Modules/clinic/_ssl.c.h
generated
41
Modules/clinic/_ssl.c.h
generated
|
|
@ -969,6 +969,45 @@ exit:
|
||||||
return return_value;
|
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__,
|
PyDoc_STRVAR(_ssl__SSLContext_get_ciphers__doc__,
|
||||||
"get_ciphers($self, /)\n"
|
"get_ciphers($self, /)\n"
|
||||||
"--\n"
|
"--\n"
|
||||||
|
|
@ -3142,4 +3181,4 @@ exit:
|
||||||
#ifndef _SSL_ENUM_CRLS_METHODDEF
|
#ifndef _SSL_ENUM_CRLS_METHODDEF
|
||||||
#define _SSL_ENUM_CRLS_METHODDEF
|
#define _SSL_ENUM_CRLS_METHODDEF
|
||||||
#endif /* !defined(_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]*/
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue