-
-
Notifications
You must be signed in to change notification settings - Fork 32.5k
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
base: main
Are you sure you want to change the base?
Conversation
@@ -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, |
There was a problem hiding this comment.
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+).
There was a problem hiding this comment.
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;
}
Doc/whatsnew/3.15.rst
Outdated
* 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* 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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
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.
f2cda88
to
d3375f1
Compare
I think all the remaining review comments should be addressed in 48e5164. Let me know if you'd like any other changes. |
.. note:: | ||
when connected, the :meth:`SSLSocket.cipher` method of SSL sockets will | ||
return details about the negotiated cipher. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.. note:: | |
when connected, the :meth:`SSLSocket.cipher` method of SSL sockets will | |
return details about the negotiated cipher. | |
.. note:: | |
When connected, the :meth:`SSLSocket.cipher` method of SSL sockets will | |
return details about the negotiated cipher. |
You can also update the other note. I never saw that we had a missing capital letter.
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`) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(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.
@@ -263,7 +263,8 @@ 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, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just for the future, but how about adding max_version
as well? and put min_version
and max_version
on their own lines? even if we don't use it, I think it's fine to add it just in case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current version of test_wrap_socket() only lets you specify min_version for now. It would be easy to add max, but it's called from 35 places. Would you change all of these to set both min & max?
The defaults for min/max version are special symbols MININUM_SUPPORTED and MAXIMUM_SUPPORTED, so there's no need to set the maximum unless you want to forcibly restrict it (in the future) to not using a TLS version greater than 1.3. I'm not sure I see a good reason to do that here, at least not yet. It would really come down to whether TLS 1.4 did something to yet again change the method used to set cipher suites, which I'm hoping won't be the case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, it was more to ease checks in internal code paths.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I'm not sure what you mean.
Lib/test/test_ssl.py
Outdated
@@ -1863,6 +1868,10 @@ class SimpleBackgroundTests(unittest.TestCase): | |||
|
|||
def setUp(self): | |||
self.server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) | |||
|
|||
if has_tls_version('TLSv1_3'): | |||
self.server_context.set_ciphersuites('TLS_AES_256_GCM_SHA384') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could raise so we should be careful (for weird build options, I think it's possible to disable TLS_AES_256_GCM_SHA384). I would suggest only calling set_ciphersuites
in tests that need it (that is those decorated by requires_tls_version
. Otherwise the server_context would unconditoinally have this ciphersuite even if a test doesn't need it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I must admit I didn't love that change, but the problem here is in testing the mismatched ciphers case. I we leave the server accepting all ciphers, then there's no way to trigger a mismatched cipher, and I think the server is set up only once and then re-used for multiple test methods.
This mismatch test could be broken into its own test class so that we only set client and server cipher suites for that one specific test, but it would mean adding more boiler plate to set up the server in two different classes.
I felt pretty comfortable letting it be shared as this change doesn't impact the set of TLS 1.2 cipher suites used by any of these tests (either client or server) and any existing code would not be attempting to set any TLS 1.3 ciphers as that functionality didn't exist. Some of those tests might use TLS 1.3 today based on the defaults set but changing the server side to a specific cipher will still allow TLS 1.3 to be used, since the client-side would not be restricting TLS 1.3 ciphers.
That said, if you'd prefer I replicate the server setup to restrict this, I'm happy to do so.
As for the specific cipher I chose, I tried to pick the most "vanilla" one I could. For a test like this to exist, we're going to have to pick SOMETHING on the client and something else on the server to create the mismatch. I could try to do something like a get_ciphers(), filtering on TLS 1.3, and then pick two different values from that list, but it would complicate the test. For the unit testing, do you expect any of the test environments to disable these cipher suites? I see other test code hardcoding ciphers like these.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and I think the server is set up only once and then re-used for multiple test methods.
The server context is new for every test though? setUp() is called before every test method. If you want to ease your life, just have different test methods, each of them will have their own server_context
that is independent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking of setUpClass() instead of setUp(), which only runs once for all tests in a class. However, even with setUp() running before each test, I'd still need to somehow pass into setUp() whether to restrict the ciphers on a test-by-test basis, and I'm not sure how to do that.
Since there's not a lot of setup code, I'm going to see what it looks like if I add a new class, with its own separate setUp() function, used only for this mismatch test. That'll minimize changes made to the existing test code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yes, I see the issue. It's because we create the server before and changing its context afterwards wouldn't help right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that's right.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok - I've checked in a new version of the test cases which uses its own class, reverting the changes to the previously modified class. This version also attempts to dynamically select ciphers from the list of available ciphers on the system. It assumes at least one cipher is available when the TLS 1.3 enabled check passes, but it will skip the mismatched cipher test if there's only one available TLS 1.3 cipher.
This commit reworks the set_ciphersuites() test cases, moving them into their own class to avoid any changes to existing tests. It also makes the cipher selection dynamic to avoid potentially trying to use a cipher not available in some environments.
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/