From 4fbb9cc7a674e93b0fd562c2da5610e8fedd483d Mon Sep 17 00:00:00 2001 From: irl Date: Fri, 27 Jun 2025 14:30:23 +0100 Subject: [PATCH] EncryptedClientHello support in ssl module Exposes options for clients to use Encrypted Client Hello (ECH) when establishing TLS connections. It is up to the user to source the ECH public keys before making use of these options. --- Doc/library/ssl.rst | 120 +++++++++ Lib/ssl.py | 49 +++- Misc/ACKS | 1 + ...5-06-12-15-39-48.gh-issue-89730.Lu35Fu.rst | 8 + Modules/_ssl.c | 245 ++++++++++++++++++ Modules/clinic/_ssl.c.h | 158 ++++++++++- configure | 57 ++++ configure.ac | 6 + pyconfig.h.in | 3 + 9 files changed, 637 insertions(+), 10 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-06-12-15-39-48.gh-issue-89730.Lu35Fu.rst diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index ae2e324d0abaa4..f21acf88fcd271 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -1003,6 +1003,13 @@ Constants .. versionadded:: 3.6 +.. class:: ECHStatus + + :class:`enum.IntEnum` collection of Encrypted Client Hello (ECH) statuses + returned by :meth:`SSLSocket.get_ech_status`. + + .. versionadded:: next + .. data:: Purpose.SERVER_AUTH Option for :func:`create_default_context` and @@ -1307,6 +1314,22 @@ SSL sockets also have the following additional methods and attributes: .. versionadded:: 3.3 +.. method:: SSLSocket.get_ech_retry_config() + + When the status returned by :meth:`SSLSocket.get_ech_status` after completion of the + handshake is :data:`ECHStatus.ECH_STATUS_GREASE_ECH`, this method returns the + configuration value provided by the server to be used for a new connection using + ECH. + + .. versionadded:: next + +.. method:: SSLSocket.get_ech_status() + + Gets the status of Encrypted Client Hello (ECH) processing. Returns an + :class:`ECHStatus` instance. + + .. versionadded:: next + .. method:: SSLSocket.selected_alpn_protocol() Return the protocol that was selected during the TLS handshake. If @@ -1379,6 +1402,15 @@ SSL sockets also have the following additional methods and attributes: .. versionadded:: 3.2 +.. attribute:: SSLSocket.outer_server_hostname + + Hostname of the server name used in the outer ClientHello when Encrypted Client + Hello (ECH) is used: :class:`str` type, or ``None`` for server-side socket or + if the outer server name was not specified in the constructor or the ECH + configuration. + + .. versionadded:: next + .. attribute:: SSLSocket.server_side A boolean which is ``True`` for server-side sockets and ``False`` for @@ -1680,6 +1712,24 @@ to speed up repeated connections from the same clients. .. versionadded:: 3.5 +.. method:: SSLContext.set_ech_config(ech_config) + + Sets an Encrypted Client Hello (ECH) configuration, which may be discovered from + an HTTPS resource record in DNS or from :meth:`SSLSocket.get_ech_retry_config`. + Multiple calls to this functions will accumulate the set of values available for + a connection. + + If the input value provided contains no suitable value (e.g. if it only contains + ECH configuration versions that are not supported), an :class:`SSLError` will be + raised. + + The ech_config parameter should be a bytes-like object containing the raw ECH + configuration. + + This method will raise :exc:`NotImplementedError` if :data:`HAS_ECH` is ``False``. + + .. versionadded:: next + .. method:: SSLContext.set_npn_protocols(protocols) Specify which protocols the socket should advertise during the SSL/TLS @@ -1699,6 +1749,28 @@ to speed up repeated connections from the same clients. NPN has been superseded by ALPN +.. method:: SSLContext.set_outer_alpn_protocols(protocols) + + Specify which protocols the socket should advertise during the TLS + handshake in the outer ClientHello when ECH is used. The *protocols* + argument accepts the same values as for + :meth:`~SSLContext.set_alpn_protocols`. + + This method will raise :exc:`NotImplementedError` if :data:`HAS_ECH` is + ``False``. + + .. versionadded:: next + +.. method:: SSLContext.set_outer_server_hostname(server_hostname) + + Specify which hostname the socket should advertise during the TLS + handshake in the outer ClientHello when ECH is used. + + This method will raise :exc:`NotImplementedError` if :data:`HAS_ECH` is + ``False``. + + .. versionadded:: next + .. attribute:: SSLContext.sni_callback Register a callback function that will be called after the TLS Client Hello @@ -2594,6 +2666,8 @@ provided. - :meth:`~SSLSocket.verify_client_post_handshake` - :meth:`~SSLSocket.unwrap` - :meth:`~SSLSocket.get_channel_binding` + - :meth:`~SSLSocket.get_ech_retry_config` + - :meth:`~SSLSocket.get_ech_status` - :meth:`~SSLSocket.version` When compared to :class:`SSLSocket`, this object lacks the following @@ -2813,6 +2887,52 @@ of TLS/SSL. Some new TLS 1.3 features are not yet available. - TLS 1.3 features like early data, deferred TLS client cert request, signature algorithm configuration, and rekeying are not supported yet. +Encrypted Client Hello +^^^^^^^^^^^^^^^^^^^^^^ + +.. versionadded:: next + +Encrypted Client Hello (ECH) allows for encrypting values that have previously only been +included unencrypted in the ClientHello records when establishing a TLS connection. To use +ECH it is necessary to provide configuration values that contain a version, algorithm +parameters, the public key to use for HPKE encryption and the "public_name" that is by +default used for the unencrypted (outer) SNI when ECH is attempted. These configuration +values may be discovered through DNS or through the "retry config" mechanism. + +The following example assumes that you have discovered a set of ECH configuration values +from DNS, or *ech_configs* may be an empty list to rely on the "retry config" mechanism:: + + import socket + import ssl + + + def connect_with_tls_ech(hostname: str, ech_configs: List[str], + use_retry_config: bool=True) -> ssl.SSLSocket: + context = ssl.create_default_context() + for ech_config in ech_configs: + context.set_ech_config(ech_config) + with socket.create_connection((hostname, 443)) as sock: + with context.wrap_socket(sock, server_hostname=hostname) as ssock: + if (ssock.get_ech_status == ECHStatus.ECH_STATUS_GREASE_ECH + and use_retry_config): + return connect_with_ech(hostname, [ssock.get_ech_retry_config()], + False) + return ssock + + hostname = "www.python.org" + ech_configs = [] # Replace with a call to a function to lookup + # ECH configurations in DNS + + ssock = connect_with_tls_ech(hostname, ech_configs) + +The following classes, methods, and attributes will be useful for using ECH: + + - :class:`ECHStatus` + - :meth:`SSLContext.set_ech_config` + - :meth:`SSLContext.set_outer_alpn_protocols` + - :meth:`SSLContext.set_outer_server_hostname` + - :meth:`SSLSocket.get_ech_status` + - :meth:`SSLSocket.get_ech_retry_config` .. seealso:: diff --git a/Lib/ssl.py b/Lib/ssl.py index 7e3c4cbd6bbf8e..3a67566a70071a 100644 --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -150,6 +150,11 @@ lambda name: name.startswith('CERT_'), source=_ssl) +_IntEnum._convert_( + 'ECHStatus', __name__, + lambda name: name.startswith('ECH_STATUS_'), + source=_ssl) + PROTOCOL_SSLv23 = _SSLMethod.PROTOCOL_SSLv23 = _SSLMethod.PROTOCOL_TLS _PROTOCOL_NAMES = {value: name for name, value in _SSLMethod.__members__.items()} @@ -502,16 +507,13 @@ def shim_cb(sslobj, servername, sslctx): self.sni_callback = shim_cb def set_alpn_protocols(self, alpn_protocols): - protos = bytearray() - for protocol in alpn_protocols: - b = bytes(protocol, 'ascii') - if len(b) == 0 or len(b) > 255: - raise SSLError('ALPN protocols must be 1 to 255 in length') - protos.append(len(b)) - protos.extend(b) - + protos = encode_alpn_protocol_list(alpn_protocols) self._set_alpn_protocols(protos) + def set_outer_alpn_protocols(self, alpn_protocols): + protos = encode_alpn_protocol_list(alpn_protocols) + self._set_outer_alpn_protocols(protos) + def _load_windows_store_certs(self, storename, purpose): try: for cert, encoding, trust in enum_certificates(storename): @@ -831,6 +833,11 @@ def context(self): def context(self, ctx): self._sslobj.context = ctx + @property + def outer_server_hostname(self): + """The server name used in the outer ClientHello.""" + return self._sslobj.get_ech_status()[2] + @property def session(self): """The SSLSession for client socket.""" @@ -968,6 +975,9 @@ def version(self): def verify_client_post_handshake(self): return self._sslobj.verify_client_post_handshake() + def get_ech_status(self): + return ECHStatus(self._sslobj.get_ech_status()[0]) + def _sslcopydoc(func): """Copy docstring from SSLObject to SSLSocket""" @@ -990,13 +1000,16 @@ def __init__(self, *args, **kwargs): @classmethod def _create(cls, sock, server_side=False, do_handshake_on_connect=True, suppress_ragged_eofs=True, server_hostname=None, - context=None, session=None): + context=None, session=None, outer_server_hostname=None): if sock.getsockopt(SOL_SOCKET, SO_TYPE) != SOCK_STREAM: raise NotImplementedError("only stream sockets are supported") if server_side: if server_hostname: raise ValueError("server_hostname can only be specified " "in client mode") + if outer_server_hostname: + raise ValueError("outer_server_hostname can only be specified " + "in client mode") if session is not None: raise ValueError("session can only be specified in " "client mode") @@ -1092,6 +1105,11 @@ def context(self, ctx): self._context = ctx self._sslobj.context = ctx + @property + def outer_server_hostname(self): + """The server name used in the outer ClientHello.""" + return self._sslobj.get_ech_status()[2] + @property @_sslcopydoc def session(self): @@ -1358,6 +1376,9 @@ def verify_client_post_handshake(self): else: raise ValueError("No SSL wrapper around " + str(self)) + def get_ech_status(self): + return ECHStatus(self._sslobj.get_ech_status()[0]) + def _real_close(self): self._sslobj = None super()._real_close() @@ -1527,3 +1548,13 @@ def get_server_certificate(addr, ssl_version=PROTOCOL_TLS_CLIENT, def get_protocol_name(protocol_code): return _PROTOCOL_NAMES.get(protocol_code, '') + +def encode_alpn_protocol_list(alpn_protocols): + protos = bytearray() + for protocol in alpn_protocols: + b = bytes(protocol, 'ascii') + if not b or len(b) > 255: + raise SSLError('ALPN protocols must be 1 to 255 in length') + protos.append(len(b)) + protos.extend(b) + return protos diff --git a/Misc/ACKS b/Misc/ACKS index 6ab50763feadd9..a7932686522396 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -1082,6 +1082,7 @@ Michael Layzell Michael Lazar Peter Lazorchak Brian Leair +Iain Learmonth Mathieu Leduc-Hamel Amandine Lee Antony Lee diff --git a/Misc/NEWS.d/next/Library/2025-06-12-15-39-48.gh-issue-89730.Lu35Fu.rst b/Misc/NEWS.d/next/Library/2025-06-12-15-39-48.gh-issue-89730.Lu35Fu.rst new file mode 100644 index 00000000000000..05c7600cdd6132 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-06-12-15-39-48.gh-issue-89730.Lu35Fu.rst @@ -0,0 +1,8 @@ +Adds support for Encrypted Client Hello (ECH) to the ssl module. Clients may +use the options exposed to establish TLS connections using ECH. Third-party +libraries like ``dnspython`` can be used to query for HTTPS and SVCB records +that contain the public keys required to use ECH with specific servers. If +no public key is available, an option is available to "GREASE" the +connection, and it is possible to retrieve the public key from the retry +configuration sent by servers that support ECH as they terminate the initial +connection. diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 014e624f6c2f00..0fa5379ee2ee5f 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -70,6 +70,10 @@ #include "openssl/bio.h" #include "openssl/dh.h" +#ifdef HAVE_OPENSSL_ECH +#include "openssl/ech.h" +#endif + #ifndef OPENSSL_THREADS # error "OPENSSL_THREADS is not defined, Python requires thread-safe OpenSSL" #endif @@ -2073,6 +2077,127 @@ cipher_to_dict(const SSL_CIPHER *cipher) ); } +/*[clinic input] +@critical_section +_ssl._SSLSocket.get_ech_status +[clinic start generated code]*/ + +static PyObject * +_ssl__SSLSocket_get_ech_status_impl(PySSLSocket *self) +/*[clinic end generated code: output=263bf7fc7888e2d6 input=71320052d6f2a10f]*/ +{ +#ifdef HAVE_OPENSSL_ECH + char *inner_sni = NULL, *outer_sni = NULL; + if (self->ssl == NULL) { + Py_RETURN_NONE; + } + int status = SSL_ech_get1_status(self->ssl, &inner_sni, &outer_sni); + PyObject *v, *retval = PyTuple_New(3); + if (retval == NULL) { + goto fail; + } + PyTuple_SET_ITEM(retval, 0, PyLong_FromLong(status)); + if (inner_sni != NULL) { + v = PyUnicode_FromString(inner_sni); + if (v == NULL) { + goto fail; + } + PyTuple_SET_ITEM(retval, 1, v); + OPENSSL_free(inner_sni); + } + else { + PyTuple_SET_ITEM(retval, 1, Py_None); + Py_INCREF(Py_None); + } + if (outer_sni != NULL) { + v = PyUnicode_FromString(outer_sni); + if (v == NULL) { + goto fail; + } + PyTuple_SET_ITEM(retval, 2, v); + OPENSSL_free(outer_sni); + } + else { + PyTuple_SET_ITEM(retval, 2, Py_None); + Py_INCREF(Py_None); + } + return retval; +fail: + if (inner_sni != NULL) { + OPENSSL_free(inner_sni); + } + if (outer_sni != NULL) { + OPENSSL_free(outer_sni); + } + return NULL; +#else + PyErr_SetString(PyExc_NotImplementedError, "OpenSSL does not have ECH support"); + return NULL; +#endif +} + +/*[clinic input] +@critical_section +_ssl._SSLSocket.get_ech_retry_config +[clinic start generated code]*/ + +static PyObject * +_ssl__SSLSocket_get_ech_retry_config_impl(PySSLSocket *self) +/*[clinic end generated code: output=967da2032df9a37a input=0e51b545e93f43ba]*/ +{ +#ifdef HAVE_OPENSSL_ECH + if (self->ssl == NULL) { + Py_RETURN_NONE; + } + unsigned char *ec; + size_t eclen; + if (SSL_ech_get1_retry_config(self->ssl, &ec, &eclen) != 1) { + _setSSLError(get_state_sock(self), NULL, 0, __FILE__, __LINE__); + return NULL; + } + return PyBytes_FromStringAndSize((char *)ec, eclen); +#else + PyErr_SetString(PyExc_NotImplementedError, "OpenSSL does not have ECH support"); + return NULL; +#endif +} + +/*[clinic input] +@critical_section +_ssl._SSLSocket.set_outer_server_name + + outer_server_name: str + / +[clinic start generated code]*/ + +static PyObject * +_ssl__SSLSocket_set_outer_server_name_impl(PySSLSocket *self, + const char *outer_server_name) +/*[clinic end generated code: output=24e7c6b7b3c2ce84 input=d527943943ffbdf3]*/ +{ +#ifdef HAVE_OPENSSL_ECH + if (self->ssl == NULL) { + Py_RETURN_NONE; + } + int result; + if (outer_server_name != NULL) { + result = SSL_ech_set1_outer_server_name(self->ssl, outer_server_name, 0); + } + else { + /* Do not send an outer SNI */ + result = SSL_ech_set1_outer_server_name(self->ssl, NULL, 1); + } + if (result != 1) { + _setSSLError(get_state_sock(self), NULL, 0, __FILE__, __LINE__); + return NULL; + } + Py_RETURN_NONE; +#else + PyErr_SetString(PyExc_NotImplementedError, "OpenSSL does not have ECH support"); + return NULL; +#endif +} + /*[clinic input] @critical_section _ssl._SSLSocket.shared_ciphers @@ -3029,6 +3154,9 @@ static PyMethodDef PySSLMethods[] = { _SSL__SSLSOCKET_COMPRESSION_METHODDEF _SSL__SSLSOCKET_SHUTDOWN_METHODDEF _SSL__SSLSOCKET_VERIFY_CLIENT_POST_HANDSHAKE_METHODDEF + _SSL__SSLSOCKET_GET_ECH_STATUS_METHODDEF + _SSL__SSLSOCKET_SET_OUTER_SERVER_NAME_METHODDEF + _SSL__SSLSOCKET_GET_ECH_RETRY_CONFIG_METHODDEF _SSL__SSLSOCKET_GET_UNVERIFIED_CHAIN_METHODDEF _SSL__SSLSOCKET_GET_VERIFIED_CHAIN_METHODDEF {NULL, NULL} @@ -3472,6 +3600,43 @@ _ssl__SSLContext__set_alpn_protocols_impl(PySSLContext *self, Py_RETURN_NONE; } +/*[clinic input] +@critical_section +_ssl._SSLContext._set_outer_alpn_protocols + protos: Py_buffer + / +[clinic start generated code]*/ + +static PyObject * +_ssl__SSLContext__set_outer_alpn_protocols_impl(PySSLContext *self, + Py_buffer *protos) +/*[clinic end generated code: output=823aea95e8dea835 input=b6ddf7d0791afd0e]*/ +{ +#ifdef HAVE_OPENSSL_ECH + if ((size_t)protos->len > UINT_MAX) { + PyErr_Format(PyExc_OverflowError, + "protocols longer than %u bytes", UINT_MAX); + return NULL; + } + PyMem_Free(self->alpn_protocols); + self->alpn_protocols = PyMem_Malloc(protos->len); + if (!self->alpn_protocols) { + return PyErr_NoMemory(); + } + memcpy(self->alpn_protocols, protos->buf, protos->len); + self->alpn_protocols_len = (unsigned int)protos->len; + if (SSL_CTX_ech_set1_outer_alpn_protos(self->ctx, self->alpn_protocols, + self->alpn_protocols_len)) + { + return PyErr_NoMemory(); + } + Py_RETURN_NONE; +#else + PyErr_SetString(PyExc_NotImplementedError, "OpenSSL does not have ECH support"); + return NULL; +#endif +} + /*[clinic input] @critical_section @getter @@ -4600,6 +4765,58 @@ _ssl__SSLContext_set_default_verify_paths_impl(PySSLContext *self) Py_RETURN_NONE; } +/*[clinic input] +@critical_section +_ssl._SSLContext.set_ech_config + + ech_config: Py_buffer + / + +Set the ECH configuration on the SSL context. + +The echconfig parameter should be a bytes-like object containing the raw ECH configuration. +[clinic start generated code]*/ + +static PyObject * +_ssl__SSLContext_set_ech_config_impl(PySSLContext *self, + Py_buffer *ech_config) +/*[clinic end generated code: output=cea53188b338cf80 input=3ef0514cae6ab6ee]*/ +{ +#ifdef HAVE_OPENSSL_ECH + BIO *es_in = BIO_new_mem_buf(ech_config->buf, ech_config->len); + OSSL_ECHSTORE *es; + if (es_in == NULL) { + goto fail; + } + es = OSSL_ECHSTORE_new(NULL, NULL)); + if (es == NULL) { + goto fail; + } + if (OSSL_ECHSTORE_read_echconfiglist(es, es_in) != 1) { + goto fail; + } + if (SSL_CTX_set1_echstore(self->ctx, es) != 1) { + goto fail; + } + OSSL_ECHSTORE_free(es); + BIO_free_all(es_in); + PyBuffer_Release(ech_config); + Py_RETURN_NONE; +fail: + _setSSLError(get_state_ctx(self), NULL, 0, __FILE__, __LINE__); + if (es != NULL) { + OSSL_ECHSTORE_free(es); + } + if (es_in != NULL) { + BIO_free_all(es_in); + } + return NULL; +#else + PyErr_SetString(PyExc_NotImplementedError, "OpenSSL does not have ECH support"); + return NULL; +#endif +} + /*[clinic input] @critical_section _ssl._SSLContext.set_ecdh_curve @@ -5256,6 +5473,8 @@ static struct PyMethodDef context_methods[] = { _SSL__SSLCONTEXT_SESSION_STATS_METHODDEF _SSL__SSLCONTEXT_SET_DEFAULT_VERIFY_PATHS_METHODDEF _SSL__SSLCONTEXT_SET_ECDH_CURVE_METHODDEF + _SSL__SSLCONTEXT_SET_ECH_CONFIG_METHODDEF + _SSL__SSLCONTEXT__SET_OUTER_ALPN_PROTOCOLS_METHODDEF _SSL__SSLCONTEXT_CERT_STORE_STATS_METHODDEF _SSL__SSLCONTEXT_GET_CA_CERTS_METHODDEF _SSL__SSLCONTEXT_GET_CIPHERS_METHODDEF @@ -6428,6 +6647,20 @@ sslmodule_init_constants(PyObject *m) ADD_INT_CONST("VERIFY_X509_PARTIAL_CHAIN", X509_V_FLAG_PARTIAL_CHAIN); #endif +#ifdef HAVE_OPENSSL_ECH + ADD_INT_CONST("ECH_STATUS_BACKEND", SSL_ECH_STATUS_BACKEND); + ADD_INT_CONST("ECH_STATUS_GREASE_ECH", SSL_ECH_STATUS_GREASE_ECH); + ADD_INT_CONST("ECH_STATUS_GREASE", SSL_ECH_STATUS_GREASE); + ADD_INT_CONST("ECH_STATUS_SUCCESS", SSL_ECH_STATUS_SUCCESS); + ADD_INT_CONST("ECH_STATUS_FAILED", SSL_ECH_STATUS_FAILED); + ADD_INT_CONST("ECH_STATUS_BAD_CALL", SSL_ECH_STATUS_BAD_CALL); + ADD_INT_CONST("ECH_STATUS_NOT_TRIED", SSL_ECH_STATUS_NOT_TRIED); + ADD_INT_CONST("ECH_STATUS_BAD_NAME", SSL_ECH_STATUS_BAD_NAME); + ADD_INT_CONST("ECH_STATUS_NOT_CONFIGURED", SSL_ECH_STATUS_NOT_CONFIGURED); + ADD_INT_CONST("ECH_STATUS_FAILED_ECH", SSL_ECH_STATUS_FAILED_ECH); + ADD_INT_CONST("ECH_STATUS_FAILED_ECH_BAD_NAME", SSL_ECH_STATUS_FAILED_ECH_BAD_NAME); +#endif + /* Alert Descriptions from ssl.h */ /* note RESERVED constants no longer intended for use have been removed */ /* http://www.iana.org/assignments/tls-parameters/tls-parameters.xml#tls-parameters-6 */ @@ -6511,6 +6744,12 @@ sslmodule_init_constants(PyObject *m) ADD_OPTION("OP_NO_TICKET", SSL_OP_NO_TICKET); ADD_OPTION("OP_LEGACY_SERVER_CONNECT", SSL_OP_LEGACY_SERVER_CONNECT); +#ifdef HAVE_OPENSSL_ECH + ADD_OPTION("OP_ECH_GREASE", SSL_OP_ECH_GREASE); + ADD_OPTION("OP_ECH_TRIALDECRYPT", SSL_OP_ECH_TRIALDECRYPT); + ADD_OPTION("OP_ECH_IGNORE_CID", SSL_OP_ECH_IGNORE_CID); + ADD_OPTION("OP_ECH_GREASE_RETRY_CONFIG", SSL_OP_ECH_GREASE_RETRY_CONFIG); +#endif #ifdef SSL_OP_SINGLE_ECDH_USE ADD_OPTION("OP_SINGLE_ECDH_USE", SSL_OP_SINGLE_ECDH_USE); #endif @@ -6638,6 +6877,12 @@ sslmodule_init_constants(PyObject *m) addbool(m, "HAS_PHA", 0); #endif +#ifdef OPENSSL_NO_ECH + addbool(m, "HAS_ECH", 0); +#else + addbool(m, "HAS_ECH", 1); +#endif + #undef addbool #undef ADD_INT_CONST diff --git a/Modules/clinic/_ssl.c.h b/Modules/clinic/_ssl.c.h index c6e2abd4d93474..a8770ac1f0e1f8 100644 --- a/Modules/clinic/_ssl.c.h +++ b/Modules/clinic/_ssl.c.h @@ -149,6 +149,91 @@ _ssl__SSLSocket_get_unverified_chain(PyObject *self, PyObject *Py_UNUSED(ignored return return_value; } +PyDoc_STRVAR(_ssl__SSLSocket_get_ech_status__doc__, +"get_ech_status($self, /)\n" +"--\n" +"\n"); + +#define _SSL__SSLSOCKET_GET_ECH_STATUS_METHODDEF \ + {"get_ech_status", (PyCFunction)_ssl__SSLSocket_get_ech_status, METH_NOARGS, _ssl__SSLSocket_get_ech_status__doc__}, + +static PyObject * +_ssl__SSLSocket_get_ech_status_impl(PySSLSocket *self); + +static PyObject * +_ssl__SSLSocket_get_ech_status(PyObject *self, PyObject *Py_UNUSED(ignored)) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _ssl__SSLSocket_get_ech_status_impl((PySSLSocket *)self); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + +PyDoc_STRVAR(_ssl__SSLSocket_get_ech_retry_config__doc__, +"get_ech_retry_config($self, /)\n" +"--\n" +"\n"); + +#define _SSL__SSLSOCKET_GET_ECH_RETRY_CONFIG_METHODDEF \ + {"get_ech_retry_config", (PyCFunction)_ssl__SSLSocket_get_ech_retry_config, METH_NOARGS, _ssl__SSLSocket_get_ech_retry_config__doc__}, + +static PyObject * +_ssl__SSLSocket_get_ech_retry_config_impl(PySSLSocket *self); + +static PyObject * +_ssl__SSLSocket_get_ech_retry_config(PyObject *self, PyObject *Py_UNUSED(ignored)) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _ssl__SSLSocket_get_ech_retry_config_impl((PySSLSocket *)self); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + +PyDoc_STRVAR(_ssl__SSLSocket_set_outer_server_name__doc__, +"set_outer_server_name($self, outer_server_name, /)\n" +"--\n" +"\n"); + +#define _SSL__SSLSOCKET_SET_OUTER_SERVER_NAME_METHODDEF \ + {"set_outer_server_name", (PyCFunction)_ssl__SSLSocket_set_outer_server_name, METH_O, _ssl__SSLSocket_set_outer_server_name__doc__}, + +static PyObject * +_ssl__SSLSocket_set_outer_server_name_impl(PySSLSocket *self, + const char *outer_server_name); + +static PyObject * +_ssl__SSLSocket_set_outer_server_name(PyObject *self, PyObject *arg) +{ + PyObject *return_value = NULL; + const char *outer_server_name; + + if (!PyUnicode_Check(arg)) { + _PyArg_BadArgument("set_outer_server_name", "argument", "str", arg); + goto exit; + } + Py_ssize_t outer_server_name_length; + outer_server_name = PyUnicode_AsUTF8AndSize(arg, &outer_server_name_length); + if (outer_server_name == NULL) { + goto exit; + } + if (strlen(outer_server_name) != (size_t)outer_server_name_length) { + PyErr_SetString(PyExc_ValueError, "embedded null character"); + goto exit; + } + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _ssl__SSLSocket_set_outer_server_name_impl((PySSLSocket *)self, outer_server_name); + Py_END_CRITICAL_SECTION(); + +exit: + return return_value; +} + PyDoc_STRVAR(_ssl__SSLSocket_shared_ciphers__doc__, "shared_ciphers($self, /)\n" "--\n" @@ -893,6 +978,40 @@ _ssl__SSLContext__set_alpn_protocols(PyObject *self, PyObject *arg) return return_value; } +PyDoc_STRVAR(_ssl__SSLContext__set_outer_alpn_protocols__doc__, +"_set_outer_alpn_protocols($self, protos, /)\n" +"--\n" +"\n"); + +#define _SSL__SSLCONTEXT__SET_OUTER_ALPN_PROTOCOLS_METHODDEF \ + {"_set_outer_alpn_protocols", (PyCFunction)_ssl__SSLContext__set_outer_alpn_protocols, METH_O, _ssl__SSLContext__set_outer_alpn_protocols__doc__}, + +static PyObject * +_ssl__SSLContext__set_outer_alpn_protocols_impl(PySSLContext *self, + Py_buffer *protos); + +static PyObject * +_ssl__SSLContext__set_outer_alpn_protocols(PyObject *self, PyObject *arg) +{ + PyObject *return_value = NULL; + Py_buffer protos = {NULL, NULL}; + + if (PyObject_GetBuffer(arg, &protos, PyBUF_SIMPLE) != 0) { + goto exit; + } + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _ssl__SSLContext__set_outer_alpn_protocols_impl((PySSLContext *)self, &protos); + Py_END_CRITICAL_SECTION(); + +exit: + /* Cleanup for protos */ + if (protos.obj) { + PyBuffer_Release(&protos); + } + + return return_value; +} + #if !defined(_ssl__SSLContext_verify_mode_DOCSTR) # define _ssl__SSLContext_verify_mode_DOCSTR NULL #endif @@ -1782,6 +1901,43 @@ _ssl__SSLContext_set_default_verify_paths(PyObject *self, PyObject *Py_UNUSED(ig return return_value; } +PyDoc_STRVAR(_ssl__SSLContext_set_ech_config__doc__, +"set_ech_config($self, ech_config, /)\n" +"--\n" +"\n" +"Set the ECH configuration on the SSL context.\n" +"\n" +"The echconfig parameter should be a bytes-like object containing the raw ECH configuration."); + +#define _SSL__SSLCONTEXT_SET_ECH_CONFIG_METHODDEF \ + {"set_ech_config", (PyCFunction)_ssl__SSLContext_set_ech_config, METH_O, _ssl__SSLContext_set_ech_config__doc__}, + +static PyObject * +_ssl__SSLContext_set_ech_config_impl(PySSLContext *self, + Py_buffer *ech_config); + +static PyObject * +_ssl__SSLContext_set_ech_config(PyObject *self, PyObject *arg) +{ + PyObject *return_value = NULL; + Py_buffer ech_config = {NULL, NULL}; + + if (PyObject_GetBuffer(arg, &ech_config, PyBUF_SIMPLE) != 0) { + goto exit; + } + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _ssl__SSLContext_set_ech_config_impl((PySSLContext *)self, &ech_config); + Py_END_CRITICAL_SECTION(); + +exit: + /* Cleanup for ech_config */ + if (ech_config.obj) { + PyBuffer_Release(&ech_config); + } + + return return_value; +} + PyDoc_STRVAR(_ssl__SSLContext_set_ecdh_curve__doc__, "set_ecdh_curve($self, name, /)\n" "--\n" @@ -2900,4 +3056,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=3406ceb5919424f6 input=a9049054013a1b77]*/ diff --git a/configure b/configure index 43b36d9231e341..07be0d41da1dfe 100755 --- a/configure +++ b/configure @@ -30892,6 +30892,63 @@ LIBS=$save_LIBS +save_CFLAGS=$CFLAGS +save_CPPFLAGS=$CPPFLAGS +save_LDFLAGS=$LDFLAGS +save_LIBS=$LIBS + + + CFLAGS="$CFLAGS $OPENSSL_INCLUDES" + + + + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for SSL_ech_get1_status" >&5 +printf %s "checking for SSL_ech_get1_status... " >&6; } +if test ${ac_cv_func_SSL_ech_get1_status+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main (void) +{ +void *x=SSL_ech_get1_status + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + ac_cv_func_SSL_ech_get1_status=yes +else case e in #( + e) ac_cv_func_SSL_ech_get1_status=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + ;; +esac +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_func_SSL_ech_get1_status" >&5 +printf "%s\n" "$ac_cv_func_SSL_ech_get1_status" >&6; } + if test "x$ac_cv_func_SSL_ech_get1_status" = xyes +then : + +printf "%s\n" "#define HAVE_OPENSSL_ECH 1" >>confdefs.h + +fi + + + + +CFLAGS=$save_CFLAGS +CPPFLAGS=$save_CPPFLAGS +LDFLAGS=$save_LDFLAGS +LIBS=$save_LIBS + + + # ssl module default cipher suite string diff --git a/configure.ac b/configure.ac index e77696e3a4e025..81d89038ec6a8c 100644 --- a/configure.ac +++ b/configure.ac @@ -7545,6 +7545,12 @@ WITH_SAVE_ENV([ ]) ]) +WITH_SAVE_ENV([ + CFLAGS="$CFLAGS $OPENSSL_INCLUDES" + + PY_CHECK_FUNC([SSL_ech_get1_status], [@%:@include ], [HAVE_OPENSSL_ECH]) +]) + # ssl module default cipher suite string AH_TEMPLATE([PY_SSL_DEFAULT_CIPHERS], [Default cipher suites list for ssl module. diff --git a/pyconfig.h.in b/pyconfig.h.in index d4f1da7fb10776..3276a7d04a4bca 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -926,6 +926,9 @@ /* Define to 1 if you have the 'openpty' function. */ #undef HAVE_OPENPTY +/* Define if you have the 'SSL_ech_get1_status' function. */ +#undef HAVE_OPENSSL_ECH + /* Define if you have the 'panel' library */ #undef HAVE_PANEL