Skip to content

gh-137197: Add SSLContext.set_ciphersuites to set TLS 1.3 ciphers #137198

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

ronf
Copy link
Contributor

@ronf ronf commented Jul 29, 2025

This commit adds a new method SSLContext.set_ciphersuites which can be used to set TLS 1.3 cipher suites. It also updates the documentation, unit tests, and "what's new" text. A NEWS blurb will be added shortly.


📚 Documentation preview 📚: https://cpython-previews--137198.org.readthedocs.build/

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

Choose a reason for hiding this comment

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

We shouldn't remove this without understanding which OpenSSL versions it was referring to and whether they are still in use by CPython builds (1.1.x-ish API'd AWS-LC at a minimum, otherwise OpenSSL 3.0+).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The clearing of the error queue is still happening here. I just took advantage of an existing helper function setSSLError to take care of this:

static PyObject *
_setSSLError (_sslmodulestate *state, const char *errstr, int errcode, const char *filename, int lineno)
{
    if (errstr == NULL)
        errcode = ERR_peek_last_error();
    else
        errcode = 0;
    fill_and_set_sslerror(state, NULL, state->PySSLErrorObject, errcode, errstr, lineno, errcode);
    ERR_clear_error();
    return NULL;
}

Comment on lines 321 to 324
* 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.
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
* 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.
* Added new method :meth:`ssl.SSLContext.set_ciphersuites` for setting TLS 1.3
ciphers. For TLS 1.2 or earlier, use :meth:`ssl.SSLContext.set_ciphers` instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What do you think about changing the text here to something like:

* 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.

I wanted to somehow capture that these calls aren't mutually exclusive if you don't know in advance what version of TLS will end up being used. The existing call only affects cipher suites chosen when TLS 1.2 or earlier is negotiated, and the new call only affects cipher suites chosen when TLS 1.3 is negotiated.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@picnixz, are you ok with this new wording?

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've checked in the new wording I proposed. Let me know if you want any changes here.

I think this change may be ready to go if there are no other review comments.

Copy link
Member

Choose a reason for hiding this comment

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

Sorry, I forgot about this PR and I missed the notification I think. Let me have a look again.

@ronf ronf requested a review from AA-Turner as a code owner August 9, 2025 00:23
ronf and others added 4 commits August 8, 2025 18:00
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.
@ronf ronf force-pushed the ssl-ciphersuite-support branch from f2cda88 to d3375f1 Compare August 9, 2025 01:00
<https://docs.openssl.org/master/man1/ciphers/>`_.
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
Copy link
Member

@picnixz picnixz Aug 9, 2025

Choose a reason for hiding this comment

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

Suggested change
connecting using TLS 1.2 and earlier. It should be a string in the `OpenSSL
connecting using TLS 1.2 and earlier. *ciphers* should be a string in the `OpenSSL

I think it's better to say what's "it" is. I personally understand but removing any possible ambiguity would be better anyway. Up to you though. Same for ciphersuites below

Comment on lines +1690 to +1691
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
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
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
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.
return the negotiated cipher and associated TLS version.
Copy link
Member

Choose a reason for hiding this comment

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

The docstring actually says:

        """Return the currently selected cipher as a 3-tuple ``(name,
        ssl_version, secret_bits)``."""

So it's not a pair but a triplet that is being returned (that's how it's implemented in C). I suggest that we make this doc change in a separate PR.

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

Choose a reason for hiding this comment

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

Maybe revert the linebreak changes (namely keep sock, * on its own line) and just add ciphersuites and min_version after ciphers, and put certfile and keyfile on their own lines as well.

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

Choose a reason for hiding this comment

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

What about builds that don't support TLS 1.3?

@@ -2109,6 +2112,28 @@ def test_ciphers(self):
cert_reqs=ssl.CERT_NONE, ciphers="^$:,;?*'dorothyx")
s.connect(self.server_addr)

def test_ciphersuites(self):
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't we ensure that this test is run for builds with TLS 1.3?

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

Choose a reason for hiding this comment

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

When does it happen upon instantiation and when does it happen while connecting? is it possible to only raise when we do s.connect or only raise when we create the socket?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy