diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index ae2e324d0abaa4..936d4f855ed5c8 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -214,7 +214,6 @@ purposes. The context now uses :data:`VERIFY_X509_PARTIAL_CHAIN` and :data:`VERIFY_X509_STRICT` in its default verify flags. - Exceptions ^^^^^^^^^^ @@ -1003,6 +1002,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:: TODO XXX + .. data:: Purpose.SERVER_AUTH Option for :func:`create_default_context` and @@ -1307,6 +1313,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:: TODO XXX + +.. method:: SSLSocket.get_ech_status() + + Gets the status of Encrypted Client Hello (ECH) processing. Returns an + :class:`ECHStatus` instance. + + .. versionadded:: TODO XXX + .. method:: SSLSocket.selected_alpn_protocol() Return the protocol that was selected during the TLS handshake. If @@ -1379,6 +1401,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:: TODO XXX + .. attribute:: SSLSocket.server_side A boolean which is ``True`` for server-side sockets and ``False`` for @@ -1680,6 +1711,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:: TODO XXX + .. method:: SSLContext.set_npn_protocols(protocols) Specify which protocols the socket should advertise during the SSL/TLS @@ -1699,6 +1748,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:: TODO XXX + +.. 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:: TODO XXX + .. attribute:: SSLContext.sni_callback Register a callback function that will be called after the TLS Client Hello @@ -2594,6 +2665,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 +2886,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:: TODO XXX + +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..831e78984697dd 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()} @@ -459,7 +464,7 @@ def wrap_socket(self, sock, server_side=False, suppress_ragged_eofs=suppress_ragged_eofs, server_hostname=server_hostname, context=self, - session=session + session=session, ) def wrap_bio(self, incoming, outgoing, server_side=False, @@ -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,14 @@ def context(self): def context(self, ctx): self._sslobj.context = ctx + @property + def outer_server_hostname(self) -> str: + """The server name used in the outer ClientHello.""" + if self._sslobj: + return self._sslobj.get_ech_status()[2] + else: + raise ValueError("No SSL wrapper around " + str(self)) + @property def session(self): """The SSLSession for client socket.""" @@ -968,6 +978,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 +1003,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 +1108,14 @@ def context(self, ctx): self._context = ctx self._sslobj.context = ctx + @property + def outer_server_hostname(self) -> str: + """The server name used in the outer ClientHello.""" + if self._sslobj: + return self._sslobj.get_ech_status()[2] + else: + raise ValueError("No SSL wrapper around " + str(self)) + @property @_sslcopydoc def session(self): @@ -1358,6 +1382,13 @@ def verify_client_post_handshake(self): else: raise ValueError("No SSL wrapper around " + str(self)) + + def get_ech_status(self): + if self._sslobj: + return ECHStatus(self._sslobj.get_ech_status()[0]) + else: + raise ValueError("No SSL wrapper around " + str(self)) + def _real_close(self): self._sslobj = None super()._real_close() @@ -1527,3 +1558,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 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) + return protos diff --git a/Misc/ACKS b/Misc/ACKS index d4557a03eb5400..11ce4001dc0057 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -1081,6 +1081,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..4c2705735b1db9 --- /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..a85ac676cc67f5 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -70,6 +70,10 @@ #include "openssl/bio.h" #include "openssl/dh.h" +#if __has_include("openssl/ech.h") +#include "openssl/ech.h" +#endif + #ifndef OPENSSL_THREADS # error "OPENSSL_THREADS is not defined, Python requires thread-safe OpenSSL" #endif @@ -2073,6 +2077,111 @@ cipher_to_dict(const SSL_CIPHER *cipher) ); } +/*[clinic input] +_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=4f68a05d59f39372]*/ +{ +#ifdef 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 *retval = PyTuple_New(3); + PyTuple_SetItem(retval, 0, PyLong_FromLong(status)); + + if (inner_sni != NULL) { + PyTuple_SetItem(retval, 1, PyUnicode_FromString(inner_sni)); + OPENSSL_free(inner_sni); + } else { + PyTuple_SetItem(retval, 1, Py_None); + Py_INCREF(Py_None); + } + + if (outer_sni != NULL) { + PyTuple_SetItem(retval, 2, PyUnicode_FromString(outer_sni)); + OPENSSL_free(outer_sni); + } else { + PyTuple_SetItem(retval, 2, Py_None); + Py_INCREF(Py_None); + } + + return retval; +#else + PyErr_SetString(PyExc_NotImplementedError, "OpenSSL does not have ECH support"); + return NULL; +#endif +} + +/*[clinic input] +_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=b9b3cee90aedc05d]*/ +{ +#ifdef OPENSSL_ECH + unsigned char *ec; + size_t eclen; + + if (self->ssl == NULL) { + Py_RETURN_NONE; + } + + SSL_ech_get1_retry_config(self->ssl, &ec, &eclen); + + return PyBytes_FromStringAndSize((char *)ec, eclen); +#else + PyErr_SetString(PyExc_NotImplementedError, "OpenSSL does not have ECH support"); + return NULL; +#endif +} + +/*[clinic input] +_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=5f68803fbe85a69c]*/ +{ +#ifdef 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 +3138,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 +3584,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 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 +4749,45 @@ _ssl__SSLContext_set_default_verify_paths_impl(PySSLContext *self) Py_RETURN_NONE; } +/*[clinic input] +_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=09f9a38c5aaef091]*/ +{ +#ifdef OPENSSL_ECH + OSSL_ECHSTORE *es = NULL; + BIO *es_in = NULL; + + if ((es_in = BIO_new_mem_buf(ech_config->buf, ech_config->len)) == NULL + || (es = OSSL_ECHSTORE_new(NULL, NULL)) == NULL + || OSSL_ECHSTORE_read_echconfiglist(es, es_in) != 1 + || SSL_CTX_set1_echstore(self->ctx, es) != 1) { + _setSSLError(get_state_ctx(self), NULL, 0, __FILE__, __LINE__); + OSSL_ECHSTORE_free(es); + BIO_free_all(es_in); + return NULL; + } + OSSL_ECHSTORE_free(es); + BIO_free_all(es_in); + PyBuffer_Release(ech_config); + Py_RETURN_NONE; +#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 +5444,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 +6618,20 @@ sslmodule_init_constants(PyObject *m) ADD_INT_CONST("VERIFY_X509_PARTIAL_CHAIN", X509_V_FLAG_PARTIAL_CHAIN); #endif +#ifdef 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 +6715,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 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 +6848,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..1818122e85fc27 100644 --- a/Modules/clinic/_ssl.c.h +++ b/Modules/clinic/_ssl.c.h @@ -149,6 +149,77 @@ _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)) +{ + return _ssl__SSLSocket_get_ech_status_impl((PySSLSocket *)self); +} + +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)) +{ + return _ssl__SSLSocket_get_ech_retry_config_impl((PySSLSocket *)self); +} + +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; + } + return_value = _ssl__SSLSocket_set_outer_server_name_impl((PySSLSocket *)self, outer_server_name); + +exit: + return return_value; +} + PyDoc_STRVAR(_ssl__SSLSocket_shared_ciphers__doc__, "shared_ciphers($self, /)\n" "--\n" @@ -893,6 +964,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 +1887,41 @@ _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; + } + return_value = _ssl__SSLContext_set_ech_config_impl((PySSLContext *)self, &ech_config); + +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 +3040,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=3b0e3cecf19da202 input=a9049054013a1b77]*/ diff --git a/configure b/configure index 029bf527da4e3d..97188e4dfce7f1 100755 --- a/configure +++ b/configure @@ -30902,6 +30902,71 @@ LIBS=$save_LIBS +save_CFLAGS=$CFLAGS +save_CPPFLAGS=$CPPFLAGS +save_LDFLAGS=$LDFLAGS +save_LIBS=$LIBS + + + LIBS="$LIBS $OPENSSL_LIBS" + CFLAGS="$CFLAGS $OPENSSL_INCLUDES" + LDFLAGS="$LDFLAGS $OPENSSL_LDFLAGS $OPENSSL_LDFLAGS_RPATH" + + for ac_header in openssl/ech.h +do : + ac_fn_c_check_header_compile "$LINENO" "openssl/ech.h" "ac_cv_header_openssl_ech_h" "$ac_includes_default" +if test "x$ac_cv_header_openssl_ech_h" = xyes +then : + printf "%s\n" "#define HAVE_OPENSSL_ECH_H 1" >>confdefs.h + have_openssl_ech_h=yes +else case e in #( + e) have_openssl_ech_h=no ;; +esac +fi + +done +if test "$have_openssl_ech_h" = "yes"; then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for OpenSSL ECH support" >&5 +printf %s "checking for OpenSSL ECH support... " >&6; } + ac_fn_check_decl "$LINENO" "SSL_ECH_STATUS_GREASE_ECH" "ac_cv_have_decl_SSL_ECH_STATUS_GREASE_ECH" " + #include + +" "$ac_c_undeclared_builtin_options" "CFLAGS" +if test "x$ac_cv_have_decl_SSL_ECH_STATUS_GREASE_ECH" = xyes +then : + ac_have_decl=1 +else case e in #( + e) ac_have_decl=0 ;; +esac +fi +printf "%s\n" "#define HAVE_DECL_SSL_ECH_STATUS_GREASE_ECH $ac_have_decl" >>confdefs.h +if test $ac_have_decl = 1 +then : + +printf "%s\n" "#define OPENSSL_ECH 1" >>confdefs.h + +else case e in #( + e) +printf "%s\n" "#define OPENSSL_NO_ECH 1" >>confdefs.h + ;; +esac +fi + + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_have_decl_SSL_ECH_STATUS_GREASE_ECH" >&5 +printf "%s\n" "$ac_cv_have_decl_SSL_ECH_STATUS_GREASE_ECH" >&6; } +else + +printf "%s\n" "#define OPENSSL_NO_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 371b2e8ed73525..b5346298e38a03 100644 --- a/configure.ac +++ b/configure.ac @@ -7551,6 +7551,29 @@ WITH_SAVE_ENV([ ]) ]) +WITH_SAVE_ENV([ + LIBS="$LIBS $OPENSSL_LIBS" + CFLAGS="$CFLAGS $OPENSSL_INCLUDES" + LDFLAGS="$LDFLAGS $OPENSSL_LDFLAGS $OPENSSL_LDFLAGS_RPATH" + +AC_CHECK_HEADERS([openssl/ech.h], [have_openssl_ech_h=yes], [have_openssl_ech_h=no]) +if test "$have_openssl_ech_h" = "yes"; then + AC_MSG_CHECKING([for OpenSSL ECH support]) + AC_CHECK_DECLS([SSL_ECH_STATUS_GREASE_ECH], + [AC_DEFINE([OPENSSL_ECH], [1], + [Define if OpenSSL has ECH support])], + [AC_DEFINE([OPENSSL_NO_ECH], [1], + [Define if OpenSSL does not have ECH support])], + [ + #include + ]) + AC_MSG_RESULT([$ac_cv_have_decl_SSL_ECH_STATUS_GREASE_ECH]) +else + AC_DEFINE([OPENSSL_NO_ECH], [1], + [Define if OpenSSL does not have ECH support]) +fi +]) + # 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 65a2c55217c258..b244ac44eb8303 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -263,6 +263,10 @@ don't. */ #undef HAVE_DECL_RTLD_NOW +/* Define to 1 if you have the declaration of 'SSL_ECH_STATUS_GREASE_ECH', and + to 0 if you don't. */ +#undef HAVE_DECL_SSL_ECH_STATUS_GREASE_ECH + /* Define to 1 if you have the declaration of 'tzname', and to 0 if you don't. */ #undef HAVE_DECL_TZNAME @@ -926,6 +930,9 @@ /* Define to 1 if you have the 'openpty' function. */ #undef HAVE_OPENPTY +/* Define to 1 if you have the header file. */ +#undef HAVE_OPENSSL_ECH_H + /* Define if you have the 'panel' library */ #undef HAVE_PANEL @@ -1675,6 +1682,12 @@ /* Define if mvwdelch in curses.h is an expression. */ #undef MVWDELCH_IS_EXPRESSION +/* Define if OpenSSL has ECH support */ +#undef OPENSSL_ECH + +/* Define if OpenSSL does not have ECH support */ +#undef OPENSSL_NO_ECH + /* Define to the address where bug reports for this package should be sent. */ #undef PACKAGE_BUGREPORT