Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 29 additions & 10 deletions Doc/library/ssl.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1684,19 +1684,33 @@ 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
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`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to myself: I would have added a linebreak after this sentence to separate from "If no cipher..." but I think the entire page needs some edits, so let's not spend too much time on wording for now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this change is already changing the line right above that, I'll go ahead and add the blank line.


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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs a .. versionadded:: next (sorry I forgot about it).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do.


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)

Expand Down Expand Up @@ -2844,10 +2858,15 @@ 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 TLS 1.3 ciphers
are allowed, the method :meth:`SSLContext.set_ciphersuites` should be
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For posterity: I really hate that OpenSSL named it SSL_CTX_set_ciphersuites for TLS 1.3 and later, and SSL_CTX_set_cipher_list for TLS 1.2 and below. I hope that this won't be annoying to users.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was surprised what they did here at first and I'm not a big fan about the naming chosen, but I can see why they didn't want to try and extend the existing command. It allows all kinds of partial matches on the five individual elements that make up a cipher suite in TLS 1.2 and earlier, plus other special keywords like LOW/MEDIUM/HIGH. All of that is gone with TLS 1.3, and the new function is much simpler and only supports an exact match against the very small number of cipher suites defined in TLS 1.3.

I don't really know why they didn't include "_list" in the name of the OpenSSL function, given that this seems to be the convention for other such calls that take a string argument.

The good news here is that things like post-quantum crypto are going to drive migration to TLS 1.3, and at some point setting TLS 1.2 cipher suites will no longer matter. At that point, the previous set_ciphers() can be deprecated and eventually removed, with only set_ciphersuites() remaining.

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this paragraph, I would say "The XXX method" instead of "The method XXX".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do.

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.
Expand Down
7 changes: 7 additions & 0 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,13 @@ ssl

(Contributed by Ron Frederick in :gh:`136306`)

* Added 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`)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
(Contributed by Ron Frederick in :gh:`137197`)
(Contributed by Ron Frederick in :gh:`137197`.)

You don't need to update the entry just above. We'll clean it up in a PR for #133879.



tarfile
-------
Expand Down
57 changes: 56 additions & 1 deletion Lib/test/test_ssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,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,
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
Expand All @@ -280,6 +282,12 @@ 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
if max_version is not None:
context.maximum_version = max_version
return context.wrap_socket(sock, **kwargs)


Expand Down Expand Up @@ -2238,6 +2246,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)

def test_ciphersuite_mismatch(self):
if self.matching_cipher == self.mismatched_cipher:
self.skipTest("Multiple TLS 1.3 ciphers are not available")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should make a separate test method for this one, otherwise the entire test is marked as skipped (but the two first checks must unconditionally pass). You can also swap the if condition:

def test_mismatched_ciphersuites(self):
    if self.matching_cipher == self.mismatched_cipher:
        self.skipTest("only one TLS 1.3 cipher is available")
    ...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do.


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)


@support.requires_resource('network')
class NetworkedTests(unittest.TestCase):

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
:mod:`ssl` can now set TLS 1.3 cipher suites.
21 changes: 21 additions & 0 deletions Modules/_ssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -3606,6 +3606,26 @@ _ssl__SSLContext_set_ciphers_impl(PySSLContext *self, const char *cipherlist)
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;
}

/*[clinic input]
@critical_section
_ssl._SSLContext.get_ciphers
Expand Down Expand Up @@ -5583,6 +5603,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
Expand Down
41 changes: 40 additions & 1 deletion Modules/clinic/_ssl.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading