Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 34k
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
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
662d66e9acb2c303072c4187ff2e452bdec9b4066ba6ad433aecc96b05c75a5304c223b516200e0fdd2535a3ae0e0baf56c5a11465e626a6File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1290,6 +1290,13 @@ SSL sockets also have the following additional methods and attributes: | ||
| .. versionadded:: 3.5 | ||
| .. method:: SSLSocket.group() | ||
| Return the group used for doing key agreement on this connection. If no | ||
| connection has been established, returns ``None``. | ||
| .. versionadded:: next | ||
| .. method:: SSLSocket.compression() | ||
| Return the compression algorithm being used as a string, or ``None`` | ||
| @@ -1647,6 +1654,25 @@ to speed up repeated connections from the same clients. | ||
| .. versionadded:: 3.6 | ||
| .. method:: SSLContext.get_groups(*, include_aliases=False) | ||
| Get a list of groups implemented for key agreement, taking into | ||
| account the current TLS :attr:`~SSLContext.minimum_version` and | ||
| :attr:`~SSLContext.maximum_version` values. For example:: | ||
| >>> ctx = ssl.create_default_context() | ||
| >>> ctx.minimum_version = ssl.TLSVersion.TLSv1_3 | ||
| >>> ctx.maximum_version = ssl.TLSVersion.TLSv1_3 | ||
| >>> ctx.get_groups() # doctest: +SKIP | ||
| ['secp256r1', 'secp384r1', 'secp521r1', 'x25519', 'x448', ...] | ||
| By default, this method returns only the preferred IANA names for the | ||
| available groups. However, if the ``include_aliases`` parameter is set to | ||
| :const:`True` this method will also return any associated aliases such as | ||
| the ECDH curve names supported in older versions of OpenSSL. | ||
| .. versionadded:: next | ||
| .. method:: SSLContext.set_default_verify_paths() | ||
| Load a set of default "certification authority" (CA) certificates from | ||
| @@ -1672,6 +1698,19 @@ to speed up repeated connections from the same clients. | ||
| TLS 1.3 cipher suites cannot be disabled with | ||
| :meth:`~SSLContext.set_ciphers`. | ||
| .. method:: SSLContext.set_groups(groups) | ||
| Set the groups allowed for key agreement for sockets created with this | ||
| context. It should be a string in the `OpenSSL group list format | ||
| <https://docs.openssl.org/master/man3/SSL_CTX_set1_groups_list/>`_. | ||
gpshead marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading. Please reload this page. | ||
| .. note:: | ||
| When connected, the :meth:`SSLSocket.group` method of SSL sockets will | ||
| return the group used for key agreement on that connection. | ||
| .. versionadded:: next | ||
| .. method:: SSLContext.set_alpn_protocols(protocols) | ||
| Specify which protocols the socket should advertise during the SSL/TLS | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Uh oh!
There was an error while loading. Please reload this page.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Uh oh!
There was an error while loading. Please reload this page.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Uh oh!
There was an error while loading. Please reload this page.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -48,6 +48,8 @@ | ||
| PROTOCOLS = sorted(ssl._PROTOCOL_NAMES) | ||
| HOST = socket_helper.HOST | ||
| IS_OPENSSL_3_0_0 = ssl.OPENSSL_VERSION_INFO >= (3, 0, 0) | ||
| CAN_GET_SELECTED_OPENSSL_GROUP = ssl.OPENSSL_VERSION_INFO >= (3, 2) | ||
| CAN_GET_AVAILABLE_OPENSSL_GROUPS = ssl.OPENSSL_VERSION_INFO >= (3, 5) | ||
| PY_SSL_DEFAULT_CIPHERS = sysconfig.get_config_var('PY_SSL_DEFAULT_CIPHERS') | ||
| PROTOCOL_TO_TLS_VERSION ={} | ||
| @@ -960,6 +962,19 @@ def test_get_ciphers(self): | ||
| len(intersection), 2, f"\ngot:{sorted(names)}\nexpected:{sorted(expected)}" | ||
| ) | ||
| def test_set_groups(self): | ||
| ctx = ssl.create_default_context() | ||
| self.assertIsNone(ctx.set_groups('P-256:X25519')) | ||
picnixz marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading. Please reload this page. | ||
| self.assertRaises(ssl.SSLError, ctx.set_groups, 'P-256:xxx') | ||
| @unittest.skipUnless(CAN_GET_AVAILABLE_OPENSSL_GROUPS, | ||
| "OpenSSL version doesn't support getting groups") | ||
| def test_get_groups(self): | ||
| ctx = ssl.create_default_context() | ||
| # By default, only return official IANA names. | ||
| self.assertNotIn('P-256', ctx.get_groups()) | ||
picnixz marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading. Please reload this page. | ||
| self.assertIn('P-256', ctx.get_groups(include_aliases=True)) | ||
| def test_options(self): | ||
| # Test default SSLContext options | ||
| ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) | ||
| @@ -2701,6 +2716,8 @@ def server_params_test(client_context, server_context, indata=b"FOO\n", | ||
| 'session_reused': s.session_reused, | ||
| 'session': s.session, | ||
| }) | ||
| if CAN_GET_SELECTED_OPENSSL_GROUP: | ||
| stats.update({'group': s.group()}) | ||
| s.close() | ||
| stats['server_alpn_protocols'] = server.selected_alpn_protocols | ||
| stats['server_shared_ciphers'] = server.shared_ciphers | ||
| @@ -3851,6 +3868,8 @@ def test_no_shared_ciphers(self): | ||
| with self.assertRaises(OSError): | ||
| s.connect((HOST, server.port)) | ||
| self.assertIn("NO_SHARED_CIPHER", server.conn_errors[0]) | ||
| self.assertIsNone(s.cipher()) | ||
| self.assertIsNone(s.group()) | ||
| def test_version_basic(self): | ||
| """ | ||
| @@ -4126,6 +4145,38 @@ def test_ecdh_curve(self): | ||
| chatty=True, connectionchatty=True, | ||
| sni_name=hostname) | ||
| def test_groups(self): | ||
| # server secp384r1, client auto | ||
| client_context, server_context, hostname = testing_context() | ||
| server_context.set_groups("secp384r1") | ||
| server_context.minimum_version = ssl.TLSVersion.TLSv1_3 | ||
| stats = server_params_test(client_context, server_context, | ||
| chatty=True, connectionchatty=True, | ||
| sni_name=hostname) | ||
| if CAN_GET_SELECTED_OPENSSL_GROUP: | ||
| self.assertEqual(stats['group'], "secp384r1") | ||
| # server auto, client secp384r1 | ||
| client_context, server_context, hostname = testing_context() | ||
| client_context.set_groups("secp384r1") | ||
| server_context.minimum_version = ssl.TLSVersion.TLSv1_3 | ||
| stats = server_params_test(client_context, server_context, | ||
| chatty=True, connectionchatty=True, | ||
| sni_name=hostname) | ||
| if CAN_GET_SELECTED_OPENSSL_GROUP: | ||
| self.assertEqual(stats['group'], "secp384r1") | ||
| # server / client curve mismatch | ||
| client_context, server_context, hostname = testing_context() | ||
| client_context.set_groups("prime256v1") | ||
| server_context.set_groups("secp384r1") | ||
| server_context.minimum_version = ssl.TLSVersion.TLSv1_3 | ||
| with self.assertRaises(ssl.SSLError): | ||
| server_params_test(client_context, server_context, | ||
| chatty=True, connectionchatty=True, | ||
| sni_name=hostname) | ||
| def test_selected_alpn_protocol(self): | ||
| # selected_alpn_protocol() is None unless ALPN is used. | ||
| client_context, server_context, hostname = testing_context() | ||
| 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 resolvedHide resolvedUh oh!There was an error while loading. Please reload this page. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -2169,6 +2169,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; | ||
| } | ||
picnixz marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading. Please reload this page. | ||
| 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 | ||
| @@ -3231,6 +3258,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 | ||
| @@ -3610,6 +3638,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 resolvedHide resolvedUh oh!There was an error while loading. Please reload this page. | ||
| / | ||
| [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 resolvedHide resolvedUh oh!There was an error while loading. Please reload this page. | ||
| 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 resolvedHide resolvedUh oh!There was an error while loading. Please reload this page. | ||
| // 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, | ||
| @@ -5457,6 +5568,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 | ||
| @@ -5467,6 +5579,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.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.