From 662d66e23f7dce202f33bcb8da436e8884bee395 Mon Sep 17 00:00:00 2001 From: Ron Frederick Date: Fri, 4 Jul 2025 15:27:35 -0700 Subject: [PATCH 01/14] gh-136306: Initial cut at SSL groups support This is an initial implementation of the feature proposed in issue #136306. --- Doc/library/ssl.rst | 31 ++++++++++ Lib/ssl.py | 12 ++++ Lib/test/test_ssl.py | 43 +++++++++++++ Modules/_ssl.c | 95 +++++++++++++++++++++++++++++ Modules/clinic/_ssl.c.h | 130 +++++++++++++++++++++++++++++++++++++++- 5 files changed, 310 insertions(+), 1 deletion(-) diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index ae2e324d0abaa4..d943b3c6897efc 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -1641,6 +1641,21 @@ to speed up repeated connections from the same clients. .. versionadded:: 3.6 +.. method:: SSLContext.get_groups() + + Get a list of groups implemented for key agreement, taking into account + the SSLContext's current TLS `minimum_version` and `maximum_version` values. + + Example:: + + >>> ctx = ssl.create_default_context() + >>> ctx.minimum_version=ssl.TLSVersion.TLSv1_3 + >>> ctx.maximum_version=ssl.TLSVersion.TLSv1_3 + >>> ctx.get_groups() + ['secp256r1', 'secp384r1', 'secp521r1', 'x25519', 'x448', 'brainpoolP256r1tls13', 'brainpoolP384r1tls13', 'brainpoolP512r1tls13', 'ffdhe2048', 'ffdhe3072', 'ffdhe4096', 'ffdhe6144', 'ffdhe8192', 'MLKEM512', 'MLKEM768', 'MLKEM1024', 'SecP256r1MLKEM768', 'X25519MLKEM768', 'SecP384r1MLKEM1024' + + .. versionadded:: 3.15 + .. method:: SSLContext.set_default_verify_paths() Load a set of default "certification authority" (CA) certificates from @@ -1666,6 +1681,18 @@ 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 + `_. + + .. note:: + when connected, the :meth:`SSLSocket.group` method of SSL sockets will + return the group used for key agreement on that connection. + + .. versionadded:: 3.15 + .. method:: SSLContext.set_alpn_protocols(protocols) Specify which protocols the socket should advertise during the SSL/TLS @@ -1789,6 +1816,10 @@ to speed up repeated connections from the same clients. .. versionadded:: 3.3 + .. deprecated:: 3.15 + + This method has been replaced by :math:`set_groups`. + .. seealso:: `SSL/TLS & Perfect Forward Secrecy `_ Vincent Bernat. diff --git a/Lib/ssl.py b/Lib/ssl.py index 7e3c4cbd6bbf8e..452118822c1277 100644 --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -931,6 +931,10 @@ def cipher(self): ssl_version, secret_bits)``.""" return self._sslobj.cipher() + def group(self): + """Return the currently selected key agreement group name.""" + return self._sslobj.group() + def shared_ciphers(self): """Return a list of ciphers shared by the client during the handshake or None if this is not a valid server connection. @@ -1206,6 +1210,14 @@ def cipher(self): else: return self._sslobj.cipher() + @_sslcopydoc + def group(self): + self._checkClosed() + if self._sslobj is None: + return None + else: + return self._sslobj.group() + @_sslcopydoc def shared_ciphers(self): self._checkClosed() diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index f123f6ece40669..41a9a0ed369b01 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -960,6 +960,15 @@ def test_get_ciphers(self): len(intersection), 2, f"\ngot: {sorted(names)}\nexpected: {sorted(expected)}" ) + def test_groups(self): + ctx = ssl.create_default_context() + self.assertIsNone(ctx.set_groups('P-256')) + self.assertIsNone(ctx.set_groups('P-256:X25519')) + + if ssl.OPENSSL_VERSION_INFO >= (3, 5): + self.assertNotIn('P-256', ctx.get_groups()) + 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 +2710,8 @@ def server_params_test(client_context, server_context, indata=b"FOO\n", 'session_reused': s.session_reused, 'session': s.session, }) + if ssl.OPENSSL_VERSION_INFO >= (3, 2): + stats.update({'group': s.group()}) s.close() stats['server_alpn_protocols'] = server.selected_alpn_protocols stats['server_shared_ciphers'] = server.shared_ciphers @@ -4126,6 +4137,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 ssl.OPENSSL_VERSION_INFO >= (3, 2): + 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 ssl.OPENSSL_VERSION_INFO >= (3, 2): + 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() diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 014e624f6c2f00..b8dcdae74f27fe 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -2142,6 +2142,31 @@ _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; + 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 +3048,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 +3428,73 @@ _ssl__SSLContext_get_ciphers_impl(PySSLContext *self) } +/*[clinic input] +@critical_section +_ssl._SSLContext.set_groups + grouplist: str + / +[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; + const char *group; + size_t i, num; + PyObject *result = NULL; + + if ((groups = sk_OPENSSL_CSTRING_new_null()) == NULL) { + _setSSLError(get_state_ctx(self), "Can't allocate stack", 0, __FILE__, __LINE__); + return NULL; + } + + if (!SSL_CTX_get0_implemented_groups(self->ctx, include_aliases, groups)) { + _setSSLError(get_state_ctx(self), "Can't get groups", 0, __FILE__, __LINE__); + sk_OPENSSL_CSTRING_free(groups); + return NULL; + } + + num = sk_OPENSSL_CSTRING_num(groups); + result = PyList_New(num); + if (result == NULL) { + _setSSLError(get_state_ctx(self), "Can't allocate list", 0, __FILE__, __LINE__); + sk_OPENSSL_CSTRING_free(groups); + return NULL; + } + + for (i = 0; i < num; ++i) { + group = sk_OPENSSL_CSTRING_value(groups, i); + PyList_SET_ITEM(result, i, PyUnicode_DecodeFSDefault(group)); + } + + sk_OPENSSL_CSTRING_free(groups); + return result; +#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 +5342,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 +5353,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 */ diff --git a/Modules/clinic/_ssl.c.h b/Modules/clinic/_ssl.c.h index c6e2abd4d93474..a4f540d22abcd2 100644 --- a/Modules/clinic/_ssl.c.h +++ b/Modules/clinic/_ssl.c.h @@ -195,6 +195,29 @@ _ssl__SSLSocket_cipher(PyObject *self, PyObject *Py_UNUSED(ignored)) return return_value; } +PyDoc_STRVAR(_ssl__SSLSocket_group__doc__, +"group($self, /)\n" +"--\n" +"\n"); + +#define _SSL__SSLSOCKET_GROUP_METHODDEF \ + {"group", (PyCFunction)_ssl__SSLSocket_group, METH_NOARGS, _ssl__SSLSocket_group__doc__}, + +static PyObject * +_ssl__SSLSocket_group_impl(PySSLSocket *self); + +static PyObject * +_ssl__SSLSocket_group(PyObject *self, PyObject *Py_UNUSED(ignored)) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _ssl__SSLSocket_group_impl((PySSLSocket *)self); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + PyDoc_STRVAR(_ssl__SSLSocket_version__doc__, "version($self, /)\n" "--\n" @@ -859,6 +882,111 @@ _ssl__SSLContext_get_ciphers(PyObject *self, PyObject *Py_UNUSED(ignored)) return return_value; } +PyDoc_STRVAR(_ssl__SSLContext_set_groups__doc__, +"set_groups($self, grouplist, /)\n" +"--\n" +"\n"); + +#define _SSL__SSLCONTEXT_SET_GROUPS_METHODDEF \ + {"set_groups", (PyCFunction)_ssl__SSLContext_set_groups, METH_O, _ssl__SSLContext_set_groups__doc__}, + +static PyObject * +_ssl__SSLContext_set_groups_impl(PySSLContext *self, const char *grouplist); + +static PyObject * +_ssl__SSLContext_set_groups(PyObject *self, PyObject *arg) +{ + PyObject *return_value = NULL; + const char *grouplist; + + if (!PyUnicode_Check(arg)) { + _PyArg_BadArgument("set_groups", "argument", "str", arg); + goto exit; + } + Py_ssize_t grouplist_length; + grouplist = PyUnicode_AsUTF8AndSize(arg, &grouplist_length); + if (grouplist == NULL) { + goto exit; + } + if (strlen(grouplist) != (size_t)grouplist_length) { + PyErr_SetString(PyExc_ValueError, "embedded null character"); + goto exit; + } + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _ssl__SSLContext_set_groups_impl((PySSLContext *)self, grouplist); + Py_END_CRITICAL_SECTION(); + +exit: + return return_value; +} + +PyDoc_STRVAR(_ssl__SSLContext_get_groups__doc__, +"get_groups($self, /, *, include_aliases=False)\n" +"--\n" +"\n"); + +#define _SSL__SSLCONTEXT_GET_GROUPS_METHODDEF \ + {"get_groups", _PyCFunction_CAST(_ssl__SSLContext_get_groups), METH_FASTCALL|METH_KEYWORDS, _ssl__SSLContext_get_groups__doc__}, + +static PyObject * +_ssl__SSLContext_get_groups_impl(PySSLContext *self, int include_aliases); + +static PyObject * +_ssl__SSLContext_get_groups(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 1 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + Py_hash_t ob_hash; + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_hash = -1, + .ob_item = { &_Py_ID(include_aliases), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"include_aliases", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "get_groups", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0; + int include_aliases = 0; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 0, /*maxpos*/ 0, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + if (!noptargs) { + goto skip_optional_kwonly; + } + include_aliases = PyObject_IsTrue(args[0]); + if (include_aliases < 0) { + goto exit; + } +skip_optional_kwonly: + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _ssl__SSLContext_get_groups_impl((PySSLContext *)self, include_aliases); + Py_END_CRITICAL_SECTION(); + +exit: + return return_value; +} + PyDoc_STRVAR(_ssl__SSLContext__set_alpn_protocols__doc__, "_set_alpn_protocols($self, protos, /)\n" "--\n" @@ -2900,4 +3028,4 @@ _ssl_enum_crls(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObje #ifndef _SSL_ENUM_CRLS_METHODDEF #define _SSL_ENUM_CRLS_METHODDEF #endif /* !defined(_SSL_ENUM_CRLS_METHODDEF) */ -/*[clinic end generated code: output=748650909fec8906 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=ef7dfc2cc05fd2f4 input=a9049054013a1b77]*/ From 9acb2c384ca417c8bb9a9778e4a0eb17d0cfeead Mon Sep 17 00:00:00 2001 From: Ron Frederick Date: Fri, 4 Jul 2025 16:29:35 -0700 Subject: [PATCH 02/14] Fix regen and lint issues --- Doc/library/ssl.rst | 3 ++- Include/internal/pycore_global_objects_fini_generated.h | 1 + Include/internal/pycore_global_strings.h | 1 + Include/internal/pycore_runtime_init_generated.h | 1 + Include/internal/pycore_unicodeobject_generated.h | 4 ++++ 5 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index d943b3c6897efc..cc5b025d1668ae 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -1644,7 +1644,8 @@ to speed up repeated connections from the same clients. .. method:: SSLContext.get_groups() Get a list of groups implemented for key agreement, taking into account - the SSLContext's current TLS `minimum_version` and `maximum_version` values. + the SSLContext's current TLS ``minimum_version`` and ``maximum_version`` + values. Example:: diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index c461bc1786ddf4..a88ae1f33bc81b 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -1007,6 +1007,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(imag)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(importlib)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(in_fd)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(include_aliases)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(incoming)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(index)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(indexgroup)); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index 72c2051bd97660..f9fa5dc479f71d 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -498,6 +498,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(imag) STRUCT_FOR_ID(importlib) STRUCT_FOR_ID(in_fd) + STRUCT_FOR_ID(include_aliases) STRUCT_FOR_ID(incoming) STRUCT_FOR_ID(index) STRUCT_FOR_ID(indexgroup) diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index d378fcae26cf35..646e41f05fcf73 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -1005,6 +1005,7 @@ extern "C" { INIT_ID(imag), \ INIT_ID(importlib), \ INIT_ID(in_fd), \ + INIT_ID(include_aliases), \ INIT_ID(incoming), \ INIT_ID(index), \ INIT_ID(indexgroup), \ diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h index e516211f6c6cbc..16a6bd5ac3405a 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -1780,6 +1780,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(include_aliases); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); string = &_Py_ID(incoming); _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); From 03072c4e187327861053d3f06b0eb0daa03852bc Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Fri, 4 Jul 2025 23:45:03 +0000 Subject: [PATCH 03/14] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2025-07-04-23-45-00.gh-issue-136306.O1YLIU.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2025-07-04-23-45-00.gh-issue-136306.O1YLIU.rst diff --git a/Misc/NEWS.d/next/Library/2025-07-04-23-45-00.gh-issue-136306.O1YLIU.rst b/Misc/NEWS.d/next/Library/2025-07-04-23-45-00.gh-issue-136306.O1YLIU.rst new file mode 100644 index 00000000000000..5556c512681b78 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-07-04-23-45-00.gh-issue-136306.O1YLIU.rst @@ -0,0 +1 @@ +:mod:`ssl` can now get and set groups used for key agreement. From 187ff2e1acaa02c8d595fc9ad0ecb645cb26e203 Mon Sep 17 00:00:00 2001 From: Ron Frederick Date: Sat, 5 Jul 2025 06:39:24 -0700 Subject: [PATCH 04/14] gh-136306: Address first round of comments --- Doc/library/ssl.rst | 22 ++++++++-------------- Modules/_ssl.c | 8 ++++++-- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index cc5b025d1668ae..432cfb71357be0 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -1645,17 +1645,15 @@ to speed up repeated connections from the same clients. Get a list of groups implemented for key agreement, taking into account the SSLContext's current TLS ``minimum_version`` and ``maximum_version`` - values. + values. For example:: - Example:: - - >>> ctx = ssl.create_default_context() - >>> ctx.minimum_version=ssl.TLSVersion.TLSv1_3 - >>> ctx.maximum_version=ssl.TLSVersion.TLSv1_3 - >>> ctx.get_groups() - ['secp256r1', 'secp384r1', 'secp521r1', 'x25519', 'x448', 'brainpoolP256r1tls13', 'brainpoolP384r1tls13', 'brainpoolP512r1tls13', 'ffdhe2048', 'ffdhe3072', 'ffdhe4096', 'ffdhe6144', 'ffdhe8192', 'MLKEM512', 'MLKEM768', 'MLKEM1024', 'SecP256r1MLKEM768', 'X25519MLKEM768', 'SecP384r1MLKEM1024' + >>> ctx = ssl.create_default_context() + >>> ctx.minimum_version=ssl.TLSVersion.TLSv1_3 + >>> ctx.maximum_version=ssl.TLSVersion.TLSv1_3 + >>> ctx.get_groups() + ['secp256r1', 'secp384r1', 'secp521r1', 'x25519', 'x448', 'brainpoolP256r1tls13', 'brainpoolP384r1tls13', 'brainpoolP512r1tls13', 'ffdhe2048', 'ffdhe3072', 'ffdhe4096', 'ffdhe6144', 'ffdhe8192', 'MLKEM512', 'MLKEM768', 'MLKEM1024', 'SecP256r1MLKEM768', 'X25519MLKEM768', 'SecP384r1MLKEM1024'] - .. versionadded:: 3.15 + .. versionadded:: next .. method:: SSLContext.set_default_verify_paths() @@ -1689,7 +1687,7 @@ to speed up repeated connections from the same clients. `_. .. note:: - when connected, the :meth:`SSLSocket.group` method of SSL sockets will + When connected, the :meth:`SSLSocket.group` method of SSL sockets will return the group used for key agreement on that connection. .. versionadded:: 3.15 @@ -1817,10 +1815,6 @@ to speed up repeated connections from the same clients. .. versionadded:: 3.3 - .. deprecated:: 3.15 - - This method has been replaced by :math:`set_groups`. - .. seealso:: `SSL/TLS & Perfect Forward Secrecy `_ Vincent Bernat. diff --git a/Modules/_ssl.c b/Modules/_ssl.c index b8dcdae74f27fe..80f0d4a3e32b1e 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -2154,11 +2154,15 @@ _ssl__SSLSocket_group_impl(PySSLSocket *self) #if OPENSSL_VERSION_NUMBER >= 0x30200000L const char *group_name; - if (self->ssl == NULL) + if (self->ssl == NULL) { Py_RETURN_NONE; + } + group_name = SSL_get0_group_name(self->ssl); - if (group_name == NULL) + if (group_name == NULL) { Py_RETURN_NONE; + } + return PyUnicode_DecodeFSDefault(group_name); #else PyErr_SetString(PyExc_NotImplementedError, From 452bdec0a2d8d1f7278dedb3f8def03154d6120f Mon Sep 17 00:00:00 2001 From: Ron Frederick Date: Sat, 5 Jul 2025 06:55:38 -0700 Subject: [PATCH 05/14] gh-136306: Add back indentation of example Looks like the indentation is required. Got a doc build error without it. --- Doc/library/ssl.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index 432cfb71357be0..e2937fd1f2365a 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -1647,11 +1647,11 @@ to speed up repeated connections from the same clients. the SSLContext's current TLS ``minimum_version`` and ``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() - ['secp256r1', 'secp384r1', 'secp521r1', 'x25519', 'x448', 'brainpoolP256r1tls13', 'brainpoolP384r1tls13', 'brainpoolP512r1tls13', 'ffdhe2048', 'ffdhe3072', 'ffdhe4096', 'ffdhe6144', 'ffdhe8192', 'MLKEM512', 'MLKEM768', 'MLKEM1024', 'SecP256r1MLKEM768', 'X25519MLKEM768', 'SecP384r1MLKEM1024'] + >>> ctx = ssl.create_default_context() + >>> ctx.minimum_version=ssl.TLSVersion.TLSv1_3 + >>> ctx.maximum_version=ssl.TLSVersion.TLSv1_3 + >>> ctx.get_groups() + ['secp256r1', 'secp384r1', 'secp521r1', 'x25519', 'x448', 'brainpoolP256r1tls13', 'brainpoolP384r1tls13', 'brainpoolP512r1tls13', 'ffdhe2048', 'ffdhe3072', 'ffdhe4096', 'ffdhe6144', 'ffdhe8192', 'MLKEM512', 'MLKEM768', 'MLKEM1024', 'SecP256r1MLKEM768', 'X25519MLKEM768', 'SecP384r1MLKEM1024'] .. versionadded:: next From 9b4066bdc8df0f93579187de52943854570dbe3a Mon Sep 17 00:00:00 2001 From: Ron Frederick Date: Sat, 5 Jul 2025 08:55:49 -0700 Subject: [PATCH 06/14] gh-136306: Handle another allocation failure in get_groups() --- Modules/_ssl.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 80f0d4a3e32b1e..772189cb16e0a9 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -3465,7 +3465,7 @@ _ssl__SSLContext_get_groups_impl(PySSLContext *self, int include_aliases) STACK_OF(OPENSSL_CSTRING) *groups; const char *group; size_t i, num; - PyObject *result = NULL; + PyObject *item, *result; if ((groups = sk_OPENSSL_CSTRING_new_null()) == NULL) { _setSSLError(get_state_ctx(self), "Can't allocate stack", 0, __FILE__, __LINE__); @@ -3488,6 +3488,15 @@ _ssl__SSLContext_get_groups_impl(PySSLContext *self, int include_aliases) for (i = 0; i < num; ++i) { group = sk_OPENSSL_CSTRING_value(groups, i); + item = PyUnicode_DecodeFSDefault(group); + + if (item == NULL) { + _setSSLError(get_state_ctx(self), "Can't allocate group name", 0, __FILE__, __LINE__); + Py_XDECREF(result); + sk_OPENSSL_CSTRING_free(groups); + return NULL; + } + PyList_SET_ITEM(result, i, PyUnicode_DecodeFSDefault(group)); } From a6ad4337ddaffd0074b827b973a3c950dc35a917 Mon Sep 17 00:00:00 2001 From: Ron Frederick Date: Sat, 5 Jul 2025 14:44:45 -0700 Subject: [PATCH 07/14] gh-136306: Address additional review comments --- Doc/library/ssl.rst | 7 ++++++- Doc/whatsnew/3.15.rst | 17 +++++++++++++++++ Lib/test/test_ssl.py | 29 +++++++++++++++++++++-------- Modules/_ssl.c | 31 ++++++++++++++++++++----------- 4 files changed, 64 insertions(+), 20 deletions(-) diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index e2937fd1f2365a..926faeaca17097 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -1641,7 +1641,7 @@ to speed up repeated connections from the same clients. .. versionadded:: 3.6 -.. method:: SSLContext.get_groups() +.. method:: SSLContext.get_groups(*, include_aliases=False) Get a list of groups implemented for key agreement, taking into account the SSLContext's current TLS ``minimum_version`` and ``maximum_version`` @@ -1653,6 +1653,11 @@ to speed up repeated connections from the same clients. >>> ctx.get_groups() ['secp256r1', 'secp384r1', 'secp521r1', 'x25519', 'x448', 'brainpoolP256r1tls13', 'brainpoolP384r1tls13', 'brainpoolP512r1tls13', 'ffdhe2048', 'ffdhe3072', 'ffdhe4096', 'ffdhe6144', 'ffdhe8192', 'MLKEM512', 'MLKEM768', 'MLKEM1024', 'SecP256r1MLKEM768', 'X25519MLKEM768', 'SecP384r1MLKEM1024'] + 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() diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 706a816f888b30..a63c0b366c89f3 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -158,6 +158,23 @@ ssl supports "External PSKs" in TLSv1.3, as described in RFC 9258. (Contributed by Will Childs-Klein in :gh:`133624`.) +* Added new methods for managing groups used for SSL key agreement + + * :meth:`SSLContext.set_groups` sets the groups allowed for doing + key agreement, extending the previous :meth:`set_ecdh_curve` method. + This new API provides the ability to list multiple groups and + supports fixed-field and post-quantum groups in addition to ECDH + curves. This method can also be used to control what key shares + are sent in the TLS handshake. + * :meth:`SSLSocket.group` returns the group selected for doing key + agreement on the current connection after the TLS handshake completes. + This call requires OpenSSL 3.2 or later. + * :meth:`SSLContext.get_groups` returns a list of all available key + agreement groups compatible with the minimum and maximum TLS versions + currently set in the context. This call requires OpenSSL 3.5 or later. + + (Contributed by Ron Frederick in :gh:`136306`) + tarfile ------- diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index 41a9a0ed369b01..e275d6c41b6e7e 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -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,14 +962,25 @@ def test_get_ciphers(self): len(intersection), 2, f"\ngot: {sorted(names)}\nexpected: {sorted(expected)}" ) - def test_groups(self): + def test_set_groups(self): ctx = ssl.create_default_context() - self.assertIsNone(ctx.set_groups('P-256')) + + # Test valid group list self.assertIsNone(ctx.set_groups('P-256:X25519')) - if ssl.OPENSSL_VERSION_INFO >= (3, 5): - self.assertNotIn('P-256', ctx.get_groups()) - self.assertIn('P-256', ctx.get_groups(include_aliases=True)) + # Test invalid group list + 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() + + # P-256 isn't an IANA name, so it shouldn't be returned by default + self.assertNotIn('P-256', ctx.get_groups()) + + # Aliases like P-256 sbould be returned when include_aliases is set + self.assertIn('P-256', ctx.get_groups(include_aliases=True)) def test_options(self): # Test default SSLContext options @@ -2710,7 +2723,7 @@ def server_params_test(client_context, server_context, indata=b"FOO\n", 'session_reused': s.session_reused, 'session': s.session, }) - if ssl.OPENSSL_VERSION_INFO >= (3, 2): + if CAN_GET_SELECTED_OPENSSL_GROUP: stats.update({'group': s.group()}) s.close() stats['server_alpn_protocols'] = server.selected_alpn_protocols @@ -4146,7 +4159,7 @@ def test_groups(self): stats = server_params_test(client_context, server_context, chatty=True, connectionchatty=True, sni_name=hostname) - if ssl.OPENSSL_VERSION_INFO >= (3, 2): + if CAN_GET_SELECTED_OPENSSL_GROUP: self.assertEqual(stats['group'], "secp384r1") # server auto, client secp384r1 @@ -4156,7 +4169,7 @@ def test_groups(self): stats = server_params_test(client_context, server_context, chatty=True, connectionchatty=True, sni_name=hostname) - if ssl.OPENSSL_VERSION_INFO >= (3, 2): + if CAN_GET_SELECTED_OPENSSL_GROUP: self.assertEqual(stats['group'], "secp384r1") # server / client curve mismatch diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 772189cb16e0a9..8e5056535cd69f 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -3462,46 +3462,55 @@ _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; + STACK_OF(OPENSSL_CSTRING) *groups = NULL; const char *group; size_t i, num; - PyObject *item, *result; + PyObject *item, *result = NULL; if ((groups = sk_OPENSSL_CSTRING_new_null()) == NULL) { _setSSLError(get_state_ctx(self), "Can't allocate stack", 0, __FILE__, __LINE__); - return NULL; + goto error; } + /* + * Note: The "groups" stack is dynamically allocated, but the strings + * returned in the stack are references to internal constants which + * should NOT be modified or freed. They should also be plain ASCII, + * so there should be no decoding issue when converting to Unicode. + */ + if (!SSL_CTX_get0_implemented_groups(self->ctx, include_aliases, groups)) { _setSSLError(get_state_ctx(self), "Can't get groups", 0, __FILE__, __LINE__); - sk_OPENSSL_CSTRING_free(groups); - return NULL; + goto error; } num = sk_OPENSSL_CSTRING_num(groups); result = PyList_New(num); if (result == NULL) { _setSSLError(get_state_ctx(self), "Can't allocate list", 0, __FILE__, __LINE__); - sk_OPENSSL_CSTRING_free(groups); - return NULL; + goto error; } for (i = 0; i < num; ++i) { group = sk_OPENSSL_CSTRING_value(groups, i); + assert(group != NULL); + item = PyUnicode_DecodeFSDefault(group); if (item == NULL) { _setSSLError(get_state_ctx(self), "Can't allocate group name", 0, __FILE__, __LINE__); - Py_XDECREF(result); - sk_OPENSSL_CSTRING_free(groups); - return NULL; + goto error; } - PyList_SET_ITEM(result, i, PyUnicode_DecodeFSDefault(group)); + 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."); From aecc96bde66a6b49ce63aeb2980870419114dc5a Mon Sep 17 00:00:00 2001 From: Ron Frederick Date: Sat, 5 Jul 2025 14:54:07 -0700 Subject: [PATCH 08/14] gh-136306: Fix method references in whatsnew entry. --- Doc/whatsnew/3.15.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index a63c0b366c89f3..f89df8a9d5d814 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -160,16 +160,17 @@ ssl * Added new methods for managing groups used for SSL key agreement - * :meth:`SSLContext.set_groups` sets the groups allowed for doing - key agreement, extending the previous :meth:`set_ecdh_curve` method. + * :meth:`ssl.SSLContext.set_groups` sets the groups allowed for doing + key agreement, extending the previous + :meth:`ssl.SSLContext.set_ecdh_curve` method. This new API provides the ability to list multiple groups and supports fixed-field and post-quantum groups in addition to ECDH curves. This method can also be used to control what key shares are sent in the TLS handshake. - * :meth:`SSLSocket.group` returns the group selected for doing key + * :meth:`ssl.SSLSocket.group` returns the group selected for doing key agreement on the current connection after the TLS handshake completes. This call requires OpenSSL 3.2 or later. - * :meth:`SSLContext.get_groups` returns a list of all available key + * :meth:`ssl.SSLContext.get_groups` returns a list of all available key agreement groups compatible with the minimum and maximum TLS versions currently set in the context. This call requires OpenSSL 3.5 or later. From 05c75a553f38fd3646776355932fb253040e9918 Mon Sep 17 00:00:00 2001 From: Ron Frederick Date: Sat, 5 Jul 2025 15:16:28 -0700 Subject: [PATCH 09/14] gh-136306: Add missing documentation for SSLSocket.group() --- Doc/library/ssl.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index 926faeaca17097..a80a25655034d3 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -1284,6 +1284,11 @@ 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``. + .. method:: SSLSocket.compression() Return the compression algorithm being used as a string, or ``None`` From 304c223cbd6f4e04225b6e9e1b103722f40be9f7 Mon Sep 17 00:00:00 2001 From: Ron Frederick Date: Sun, 6 Jul 2025 10:51:30 -0700 Subject: [PATCH 10/14] gh-136306: Address additional review comments --- Doc/library/ssl.rst | 12 +++++++----- Modules/_ssl.c | 20 ++++++++------------ 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index a80a25655034d3..3a79e8f082d6ba 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -1289,6 +1289,8 @@ SSL sockets also have the following additional methods and attributes: 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`` @@ -1653,10 +1655,10 @@ to speed up repeated connections from the same clients. 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() - ['secp256r1', 'secp384r1', 'secp521r1', 'x25519', 'x448', 'brainpoolP256r1tls13', 'brainpoolP384r1tls13', 'brainpoolP512r1tls13', 'ffdhe2048', 'ffdhe3072', 'ffdhe4096', 'ffdhe6144', 'ffdhe8192', 'MLKEM512', 'MLKEM768', 'MLKEM1024', 'SecP256r1MLKEM768', 'X25519MLKEM768', 'SecP384r1MLKEM1024'] + >>> 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 @@ -1700,7 +1702,7 @@ to speed up repeated connections from the same clients. When connected, the :meth:`SSLSocket.group` method of SSL sockets will return the group used for key agreement on that connection. - .. versionadded:: 3.15 + .. versionadded:: next .. method:: SSLContext.set_alpn_protocols(protocols) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 8e5056535cd69f..fd82890d100167 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -2157,12 +2157,10 @@ _ssl__SSLSocket_group_impl(PySSLSocket *self) if (self->ssl == NULL) { Py_RETURN_NONE; } - group_name = SSL_get0_group_name(self->ssl); if (group_name == NULL) { Py_RETURN_NONE; } - return PyUnicode_DecodeFSDefault(group_name); #else PyErr_SetString(PyExc_NotImplementedError, @@ -3467,18 +3465,13 @@ _ssl__SSLContext_get_groups_impl(PySSLContext *self, int include_aliases) size_t 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; + goto error; } - /* - * Note: The "groups" stack is dynamically allocated, but the strings - * returned in the stack are references to internal constants which - * should NOT be modified or freed. They should also be plain ASCII, - * so there should be no decoding issue when converting to Unicode. - */ - 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; @@ -3492,11 +3485,14 @@ _ssl__SSLContext_get_groups_impl(PySSLContext *self, int include_aliases) } 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); + assert(group != NULL); + // Group names are plain ASCII, so there's no chance of a decoding + // error here. However, an allocation failure could occur when + // constructing the Unicode version of the names. item = PyUnicode_DecodeFSDefault(group); - if (item == NULL) { _setSSLError(get_state_ctx(self), "Can't allocate group name", 0, __FILE__, __LINE__); goto error; From b516200a4317cd2021d614ece3c7599a6a9e023c Mon Sep 17 00:00:00 2001 From: Ron Frederick Date: Mon, 7 Jul 2025 06:24:40 -0700 Subject: [PATCH 11/14] gh-136306: Minor doc updates --- Doc/library/ssl.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index 3a79e8f082d6ba..a80c65017b0e06 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -1650,14 +1650,14 @@ to speed up repeated connections from the same clients. .. method:: SSLContext.get_groups(*, include_aliases=False) - Get a list of groups implemented for key agreement, taking into account - the SSLContext's current TLS ``minimum_version`` and ``maximum_version`` - values. For example:: + 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 + >>> ctx.get_groups() # doctest: +SKIP ['secp256r1', 'secp384r1', 'secp521r1', 'x25519', 'x448', ...] By default, this method returns only the preferred IANA names for the From e0fdd25d162bf85996e075030940c6dc6c537be1 Mon Sep 17 00:00:00 2001 From: Ron Frederick Date: Mon, 7 Jul 2025 06:26:19 -0700 Subject: [PATCH 12/14] gh-136306: Missed one --- Doc/library/ssl.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index a80c65017b0e06..f2a1a190b525c7 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -1699,6 +1699,7 @@ to speed up repeated connections from the same clients. `_. .. note:: + When connected, the :meth:`SSLSocket.group` method of SSL sockets will return the group used for key agreement on that connection. From 35a3ae0896dfd4cd4522aa3479ec32aa080308f5 Mon Sep 17 00:00:00 2001 From: Ron Frederick Date: Sat, 12 Jul 2025 10:38:31 -0700 Subject: [PATCH 13/14] gh-136306: Address additional review comments --- Lib/test/test_ssl.py | 9 +-------- Modules/_ssl.c | 4 ++-- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index e275d6c41b6e7e..0fad9e7cb4b1c6 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -964,22 +964,15 @@ def test_get_ciphers(self): def test_set_groups(self): ctx = ssl.create_default_context() - - # Test valid group list self.assertIsNone(ctx.set_groups('P-256:X25519')) - - # Test invalid group list 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() - - # P-256 isn't an IANA name, so it shouldn't be returned by default + # By default, only return official IANA names. self.assertNotIn('P-256', ctx.get_groups()) - - # Aliases like P-256 sbould be returned when include_aliases is set self.assertIn('P-256', ctx.get_groups(include_aliases=True)) def test_options(self): diff --git a/Modules/_ssl.c b/Modules/_ssl.c index fd82890d100167..1229ef9e412b78 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -3462,7 +3462,7 @@ _ssl__SSLContext_get_groups_impl(PySSLContext *self, int include_aliases) #if OPENSSL_VERSION_NUMBER >= 0x30500000L STACK_OF(OPENSSL_CSTRING) *groups = NULL; const char *group; - size_t i, num; + int i, num; PyObject *item, *result = NULL; // This "groups" object is dynamically allocated, but the strings inside @@ -3492,7 +3492,7 @@ _ssl__SSLContext_get_groups_impl(PySSLContext *self, int include_aliases) // Group names are plain ASCII, so there's no chance of a decoding // error here. However, an allocation failure could occur when // constructing the Unicode version of the names. - item = PyUnicode_DecodeFSDefault(group); + 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; From e0baf56bc7d3191ccc6aed919caf04be8af2ec1f Mon Sep 17 00:00:00 2001 From: Ron Frederick Date: Sat, 12 Jul 2025 11:28:47 -0700 Subject: [PATCH 14/14] Add checks that cipher() and group() return None on handshake failure --- Lib/test/test_ssl.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index 0fad9e7cb4b1c6..790b91b7604ebe 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -3868,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): """ 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