Content-Length: 726559 | pFad | http://github.com/python/cpython/pull/136307/files/187ff2e1acaa02c8d595fc9ad0ecb645cb26e203

9C gh-136306: Add support for SSL groups by ronf · Pull Request #136307 · python/cpython · GitHub
Skip to content

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

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions Doc/library/ssl.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1641,6 +1641,20 @@

.. 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. 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']

.. versionadded:: next

.. method:: SSLContext.set_default_verify_paths()

Load a set of default "certification authority" (CA) certificates from
Expand All @@ -1666,6 +1680,18 @@
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/>`_.

.. note::
When connected, the :meth:`SSLSocket.group` method of SSL sockets will

Check warning on line 1690 in Doc/library/ssl.rst

View workflow job for this annotation

GitHub Actions / Docs / Docs

py:meth reference target not found: SSLSocket.group [ref.meth]
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
Expand Down
1 change: 1 addition & 0 deletions Include/internal/pycore_global_objects_fini_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Include/internal/pycore_global_strings.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions Include/internal/pycore_runtime_init_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Include/internal/pycore_unicodeobject_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions Lib/ssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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()
Expand Down
43 changes: 43 additions & 0 deletions Lib/test/test_ssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down
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.
99 changes: 99 additions & 0 deletions Modules/_ssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -2142,6 +2142,35 @@ _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
Expand Down Expand Up @@ -3023,6 +3052,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
Expand Down Expand Up @@ -3402,6 +3432,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,
Expand Down Expand Up @@ -5249,6 +5346,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
Expand All @@ -5259,6 +5357,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 */
Expand Down
Loading
Loading








ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: http://github.com/python/cpython/pull/136307/files/187ff2e1acaa02c8d595fc9ad0ecb645cb26e203

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy