Skip to content
Merged
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 numberDiff line numberDiff 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`.
Comment thread
picnixz marked this conversation as resolved.

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)
Comment thread
picnixz marked this conversation as resolved.

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 DownExpand 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 :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
Comment thread
picnixz marked this conversation as resolved.
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 numberDiff line numberDiff line change
Expand Up@@ -332,6 +332,13 @@ ssl

(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
-------
Expand Down
72 changes: 71 additions & 1 deletion Lib/test/test_ssl.py
Original file line numberDiff line numberDiff 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 DownExpand Up@@ -2238,6 +2246,68 @@ def test_transport_eof(self):
self.assertRaises(ssl.SSLEOFError, sslobj.read)


@unittest.skipUnless(has_tls_version('TLSv1_3'), "TLS 1.3 is not available")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Note to myself: change the requires_tls_version decorator so that it works.

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')

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This test is asserting that the default TLSv1_2 cipher suite is actually being used right? I'm fine with the current test but I'd like to fortify it (which I'll do) in the future where we have a ciphersuite for TLSv1.3 and a ciphersuite for TLSv1.2 and if we use min/max versions, then we're going to be using the TLSv1.2 ciphersuite we specified.


def test_ciphersuite_mismatch(self):
if self.matching_cipher == self.mismatched_cipher:
self.skipTest("Multiple TLS 1.3 ciphers are not available")
Comment thread
picnixz marked this conversation as resolved.

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):

Expand Down
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
:class:`~ssl.SSLContext` objects can now set TLS 1.3 cipher suites
via :meth:`~ssl.SSLContext.set_ciphersuites`.
20 changes: 20 additions & 0 deletions Modules/_ssl.c
Original file line numberDiff line numberDiff line change
Expand Up@@ -3606,6 +3606,25 @@ _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]*/
{
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
Expand DownExpand Up@@ -5583,6 +5602,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