-
-
Notifications
You must be signed in to change notification settings - Fork 32.3k
gh-136306: Add support for SSL groups #136307
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?
Changes from all commits
662d66e
9acb2c3
03072c4
187ff2e
452bdec
9b4066b
a6ad433
aecc96b
05c75a5
304c223
b516200
e0fdd25
35a3ae0
e0baf56
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
:mod:`ssl` can now get and set groups used for key agreement. | ||
picnixz marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2142,6 +2142,33 @@ _ssl__SSLSocket_cipher_impl(PySSLSocket *self) | |
return cipher_to_tuple(current); | ||
} | ||
|
||
/*[clinic input] | ||
@critical_section | ||
_ssl._SSLSocket.group | ||
[clinic start generated code]*/ | ||
|
||
static PyObject * | ||
_ssl__SSLSocket_group_impl(PySSLSocket *self) | ||
/*[clinic end generated code: output=9c168ee877017b95 input=5f187d8bf0d433b7]*/ | ||
{ | ||
#if OPENSSL_VERSION_NUMBER >= 0x30200000L | ||
const char *group_name; | ||
|
||
if (self->ssl == NULL) { | ||
Py_RETURN_NONE; | ||
} | ||
group_name = SSL_get0_group_name(self->ssl); | ||
if (group_name == NULL) { | ||
Py_RETURN_NONE; | ||
} | ||
Comment on lines
+2161
to
+2163
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. According to the docs, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note 2: the docs only mentions the case when the TLS session wasn't established yet, but to be on the safe side, I'd prefer checking if there's an error on OpenSSL's side. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are you suggesting adding a unit test here which verifies that OpenSSL returning NULL will hit this code and return None, or are you thinking this should return an error instead? Right now, this API matches the behavior of SSLSocket.cipher(), returning There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah no, I'm just telling to check that we don't have an SSL error set before returning None. If there is an SSL error, we can raise an exception. Otherwise, you can return None as you did. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looking at the documentation, I can see where you'd think SSL_get0_group_name() might set an SSL error before returning NULL. Looking at the implementation, though, the functions called by SSL_get0_group_name() return NULL without actually ever setting an error themselves. They just check if other state is NULL or not, and return the name if one is available. I think the error referred to in the docs would have been returned in a prior call, such as when initiating the handshake. I could put in an explicit call to ERR_get_error here, but I can't find any other code in this module which looks at that to decide whether to return an error. There's always a check for a special error return value in the function before looking at any SSL error information. In this case, since NULL is returned without setting an SSL error, I would recommend we treat all NULL values as indicating the handshake didn't complete or wasn't attempted yet, and always return There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Ah, so it would be hard to detect it. Ok, we can return None, and anyway, if there's an issue, subsequent calls to the interface will fail I think.
That could be more useful for the user. Can you make some test for that (namely, you set a group, and try to get its name even though the session doesn't exist and see that it returns None). We should also document that it returns None if no session exists yet. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The doc about returning .. method:: SSLSocket.group()
Return the group used for doing key agreement on this connection. If no
connection has been established, returns ``None``.
.. versionadded:: next This is very similar to the docs for SSLSocket.cipher(). I should be able to come up with a unit test which creates an SSLSocket without connecting it and then tries to query the group - working on that now. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Trying to get an SSLSocket created without an underlying connection is actually somewhat messy. I think a more realistic case would actually be to try and query the cipher & group after a handshake failure, like no matching ciphers between the client and server. There's already a test for that, and it's straightforward to add in checks that both |
||
return PyUnicode_DecodeFSDefault(group_name); | ||
#else | ||
PyErr_SetString(PyExc_NotImplementedError, | ||
"Getting selected group requires OpenSSL 3.2 or later."); | ||
return NULL; | ||
#endif | ||
} | ||
|
||
/*[clinic input] | ||
@critical_section | ||
_ssl._SSLSocket.version | ||
|
@@ -3023,6 +3050,7 @@ static PyMethodDef PySSLMethods[] = { | |
_SSL__SSLSOCKET_GETPEERCERT_METHODDEF | ||
_SSL__SSLSOCKET_GET_CHANNEL_BINDING_METHODDEF | ||
_SSL__SSLSOCKET_CIPHER_METHODDEF | ||
_SSL__SSLSOCKET_GROUP_METHODDEF | ||
_SSL__SSLSOCKET_SHARED_CIPHERS_METHODDEF | ||
_SSL__SSLSOCKET_VERSION_METHODDEF | ||
_SSL__SSLSOCKET_SELECTED_ALPN_PROTOCOL_METHODDEF | ||
|
@@ -3402,6 +3430,89 @@ _ssl__SSLContext_get_ciphers_impl(PySSLContext *self) | |
|
||
} | ||
|
||
/*[clinic input] | ||
@critical_section | ||
_ssl._SSLContext.set_groups | ||
grouplist: str | ||
picnixz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/ | ||
[clinic start generated code]*/ | ||
|
||
static PyObject * | ||
_ssl__SSLContext_set_groups_impl(PySSLContext *self, const char *grouplist) | ||
/*[clinic end generated code: output=0b5d05dfd371ffd0 input=2cc64cef21930741]*/ | ||
{ | ||
if (!SSL_CTX_set1_groups_list(self->ctx, grouplist)) { | ||
_setSSLError(get_state_ctx(self), "unrecognized group", 0, __FILE__, __LINE__); | ||
return NULL; | ||
} | ||
Py_RETURN_NONE; | ||
} | ||
|
||
/*[clinic input] | ||
@critical_section | ||
_ssl._SSLContext.get_groups | ||
* | ||
include_aliases: bool = False | ||
[clinic start generated code]*/ | ||
|
||
static PyObject * | ||
_ssl__SSLContext_get_groups_impl(PySSLContext *self, int include_aliases) | ||
/*[clinic end generated code: output=6d6209dd1051529b input=3e8ee5deb277dcc5]*/ | ||
{ | ||
#if OPENSSL_VERSION_NUMBER >= 0x30500000L | ||
STACK_OF(OPENSSL_CSTRING) *groups = NULL; | ||
const char *group; | ||
int i, num; | ||
PyObject *item, *result = NULL; | ||
|
||
// This "groups" object is dynamically allocated, but the strings inside | ||
// it are internal constants which shouldn't ever be modified or freed. | ||
if ((groups = sk_OPENSSL_CSTRING_new_null()) == NULL) { | ||
_setSSLError(get_state_ctx(self), "Can't allocate stack", 0, __FILE__, __LINE__); | ||
goto error; | ||
} | ||
|
||
if (!SSL_CTX_get0_implemented_groups(self->ctx, include_aliases, groups)) { | ||
_setSSLError(get_state_ctx(self), "Can't get groups", 0, __FILE__, __LINE__); | ||
goto error; | ||
} | ||
|
||
num = sk_OPENSSL_CSTRING_num(groups); | ||
picnixz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
result = PyList_New(num); | ||
if (result == NULL) { | ||
_setSSLError(get_state_ctx(self), "Can't allocate list", 0, __FILE__, __LINE__); | ||
goto error; | ||
} | ||
|
||
for (i = 0; i < num; ++i) { | ||
// There's no allocation here, so group won't ever be NULL. | ||
group = sk_OPENSSL_CSTRING_value(groups, i); | ||
assert(group != NULL); | ||
|
||
// Group names are plain ASCII, so there's no chance of a decoding | ||
picnixz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// error here. However, an allocation failure could occur when | ||
// constructing the Unicode version of the names. | ||
item = PyUnicode_DecodeASCII(group, strlen(group), "strict"); | ||
if (item == NULL) { | ||
_setSSLError(get_state_ctx(self), "Can't allocate group name", 0, __FILE__, __LINE__); | ||
goto error; | ||
} | ||
|
||
PyList_SET_ITEM(result, i, item); | ||
} | ||
|
||
sk_OPENSSL_CSTRING_free(groups); | ||
return result; | ||
error: | ||
Py_XDECREF(result); | ||
sk_OPENSSL_CSTRING_free(groups); | ||
return NULL; | ||
#else | ||
PyErr_SetString(PyExc_NotImplementedError, | ||
"Getting implemented groups requires OpenSSL 3.5 or later."); | ||
return NULL; | ||
#endif | ||
} | ||
|
||
static int | ||
do_protocol_selection(int alpn, unsigned char **out, unsigned char *outlen, | ||
|
@@ -5249,6 +5360,7 @@ static struct PyMethodDef context_methods[] = { | |
_SSL__SSLCONTEXT__WRAP_SOCKET_METHODDEF | ||
_SSL__SSLCONTEXT__WRAP_BIO_METHODDEF | ||
_SSL__SSLCONTEXT_SET_CIPHERS_METHODDEF | ||
_SSL__SSLCONTEXT_SET_GROUPS_METHODDEF | ||
_SSL__SSLCONTEXT__SET_ALPN_PROTOCOLS_METHODDEF | ||
_SSL__SSLCONTEXT_LOAD_CERT_CHAIN_METHODDEF | ||
_SSL__SSLCONTEXT_LOAD_DH_PARAMS_METHODDEF | ||
|
@@ -5259,6 +5371,7 @@ static struct PyMethodDef context_methods[] = { | |
_SSL__SSLCONTEXT_CERT_STORE_STATS_METHODDEF | ||
_SSL__SSLCONTEXT_GET_CA_CERTS_METHODDEF | ||
_SSL__SSLCONTEXT_GET_CIPHERS_METHODDEF | ||
_SSL__SSLCONTEXT_GET_GROUPS_METHODDEF | ||
_SSL__SSLCONTEXT_SET_PSK_CLIENT_CALLBACK_METHODDEF | ||
_SSL__SSLCONTEXT_SET_PSK_SERVER_CALLBACK_METHODDEF | ||
{NULL, NULL} /* sentinel */ | ||
|
Uh oh!
There was an error while loading. Please reload this page.