Skip to content

Commit 5649b6e

Browse files
committed
Let Codex go nuts.
1 parent 4b91de4 commit 5649b6e

10 files changed

Lines changed: 369 additions & 211 deletions

File tree

‎Doc/library/ssl.rst‎

Lines changed: 96 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -869,6 +869,40 @@ Constants
869869

870870
.. versionadded:: 3.12
871871

872+
.. data:: OP_ECH_GREASE
873+
874+
Include a GREASE ECH extension in ClientHello messages when ECH is not
875+
being attempted. This can elicit an ECH retry configuration from a server.
876+
877+
Only available when :data:`HAS_ECH` is ``True``.
878+
879+
.. versionadded:: next
880+
881+
.. data:: OP_ECH_TRIALDECRYPT
882+
883+
Attempt server-side ECH decryption with all available ECH keys, rather
884+
than only those with a matching configuration identifier.
885+
886+
Only available when :data:`HAS_ECH` is ``True``.
887+
888+
.. versionadded:: next
889+
890+
.. data:: OP_ECH_IGNORE_CID
891+
892+
Use a random ECH configuration identifier in client connections.
893+
894+
Only available when :data:`HAS_ECH` is ``True``.
895+
896+
.. versionadded:: next
897+
898+
.. data:: OP_ECH_GREASE_RETRY_CONFIG
899+
900+
Include GREASE entries in the ECH retry configurations sent by a server.
901+
902+
Only available when :data:`HAS_ECH` is ``True``.
903+
904+
.. versionadded:: next
905+
872906
.. data:: HAS_ALPN
873907

874908
Whether the OpenSSL library has built-in support for the *Application-Layer
@@ -964,6 +998,13 @@ Constants
964998

965999
.. versionadded:: 3.14
9661000

1001+
.. data:: HAS_ECH
1002+
1003+
Whether the OpenSSL library has built-in support for Encrypted Client
1004+
Hello (ECH). This requires OpenSSL 4.0 or later with ECH enabled.
1005+
1006+
.. versionadded:: next
1007+
9671008
.. data:: CHANNEL_BINDING_TYPES
9681009

9691010
List of supported TLS channel binding types. Strings in this list
@@ -1039,6 +1080,12 @@ Constants
10391080

10401081
.. versionadded:: next
10411082

1083+
.. attribute:: ECHStatus.ECH_STATUS_GREASE_ECH
1084+
1085+
The client sent a GREASE ECH extension and received a retry configuration.
1086+
1087+
.. versionadded:: next
1088+
10421089
.. data:: Purpose.SERVER_AUTH
10431090

10441091
Option for :func:`create_default_context` and
@@ -1377,10 +1424,13 @@ SSL sockets also have the following additional methods and attributes:
13771424

13781425
.. method:: SSLSocket.get_ech_retry_config()
13791426

1380-
When the status returned by :meth:`SSLSocket.get_ech_status` after completion of the
1381-
handshake is :data:`ECHStatus.ECH_STATUS_GREASE_ECH`, this method returns the
1382-
configuration value provided by the server to be used for a new connection using
1383-
ECH.
1427+
Return the server's ECH retry configuration as :class:`bytes`, or empty
1428+
bytes if no retry configuration is available. When the status returned by
1429+
:meth:`SSLSocket.get_ech_status` after the handshake is
1430+
:attr:`ECHStatus.ECH_STATUS_GREASE_ECH`, the configuration can be used
1431+
for a new connection using ECH.
1432+
1433+
Raise :exc:`NotImplementedError` if :data:`HAS_ECH` is ``False``.
13841434

13851435
.. versionadded:: next
13861436

@@ -1389,6 +1439,8 @@ SSL sockets also have the following additional methods and attributes:
13891439
Gets the status of Encrypted Client Hello (ECH) processing. Returns an
13901440
:class:`ECHStatus` instance.
13911441

1442+
Raise :exc:`NotImplementedError` if :data:`HAS_ECH` is ``False``.
1443+
13921444
.. versionadded:: next
13931445

13941446
.. method:: SSLSocket.selected_alpn_protocol()
@@ -1467,8 +1519,10 @@ SSL sockets also have the following additional methods and attributes:
14671519

14681520
Hostname of the server name used in the outer ClientHello when Encrypted Client
14691521
Hello (ECH) is used: :class:`str` type, or ``None`` for server-side socket or
1470-
if the outer server name was not specified in the constructor or the ECH
1471-
configuration.
1522+
if no outer server name is available.
1523+
1524+
Raise :exc:`NotImplementedError` if :data:`HAS_ECH` is ``False`` and the
1525+
socket has an SSL wrapper.
14721526

14731527
.. versionadded:: next
14741528

@@ -1852,15 +1906,14 @@ to speed up repeated connections from the same clients.
18521906

18531907
Sets an Encrypted Client Hello (ECH) configuration, which may be discovered from
18541908
an HTTPS resource record in DNS or from :meth:`SSLSocket.get_ech_retry_config`.
1855-
Multiple calls to this functions will accumulate the set of values available for
1856-
a connection.
1909+
Each call replaces the context's previous configuration.
18571910

18581911
If the input value provided contains no suitable value (e.g. if it only contains
18591912
ECH configuration versions that are not supported), an :class:`SSLError` will be
18601913
raised.
18611914

1862-
The ech_config parameter should be a bytes-like object containing the raw ECH
1863-
configuration.
1915+
The *ech_config* parameter should be a :term:`bytes-like object` containing
1916+
a binary-encoded ECHConfigList.
18641917

18651918
This method will raise :exc:`NotImplementedError` if :data:`HAS_ECH` is ``False``.
18661919

@@ -1897,16 +1950,6 @@ to speed up repeated connections from the same clients.
18971950

18981951
.. versionadded:: next
18991952

1900-
.. method:: SSLContext.set_outer_server_hostname(server_hostname)
1901-
1902-
Specify which hostname the socket should advertise during the TLS
1903-
handshake in the outer ClientHello when ECH is used.
1904-
1905-
This method will raise :exc:`NotImplementedError` if :data:`HAS_ECH` is
1906-
``False``.
1907-
1908-
.. versionadded:: next
1909-
19101953
.. attribute:: SSLContext.sni_callback
19111954

19121955
Register a callback function that will be called after the TLS Client Hello
@@ -3051,50 +3094,61 @@ of TLS/SSL. Some new TLS 1.3 features are not yet available.
30513094
- TLS 1.3 features like early data, deferred TLS client cert request,
30523095
and rekeying are not supported yet.
30533096

3097+
.. _ssl-encrypted-client-hello:
3098+
30543099
Encrypted Client Hello
30553100
^^^^^^^^^^^^^^^^^^^^^^
30563101

30573102
.. versionadded:: next
30583103

3059-
Encrypted Client Hello (ECH) allows for encrypting values that have previously only been
3060-
included unencrypted in the ClientHello records when establishing a TLS connection. To use
3061-
ECH it is necessary to provide configuration values that contain a version, algorithm
3062-
parameters, the public key to use for HPKE encryption and the "public_name" that is by
3063-
default used for the unencrypted (outer) SNI when ECH is attempted. These configuration
3064-
values may be discovered through DNS or through the "retry config" mechanism.
3104+
Encrypted Client Hello (ECH) encrypts values in the ClientHello record sent when
3105+
establishing a TLS connection. It requires an ECHConfigList containing algorithm
3106+
parameters, a public key, and a public name used in the unencrypted outer
3107+
ClientHello. ECH requires OpenSSL 4.0 or later with ECH enabled; check
3108+
:data:`HAS_ECH` before using it.
30653109

3066-
The following example assumes that you have discovered a set of ECH configuration values
3067-
from DNS, or *ech_configs* may be an empty list to rely on the "retry config" mechanism::
3110+
Configuration values may be discovered through DNS or through the server's
3111+
retry configuration. The following example accepts a binary-encoded
3112+
ECHConfigList discovered through DNS. If none is supplied, it enables ECH
3113+
GREASE to request a retry configuration from the server::
30683114

30693115
import socket
30703116
import ssl
30713117

30723118

3073-
def connect_with_tls_ech(hostname: str, ech_configs: List[str],
3074-
use_retry_config: bool=True) -> ssl.SSLSocket:
3119+
def connect_with_tls_ech(hostname, ech_config=None, use_retry_config=True):
30753120
context = ssl.create_default_context()
3076-
for ech_config in ech_configs:
3121+
if ech_config is not None:
30773122
context.set_ech_config(ech_config)
3078-
with socket.create_connection((hostname, 443)) as sock:
3079-
with context.wrap_socket(sock, server_hostname=hostname) as ssock:
3080-
if (ssock.get_ech_status == ECHStatus.ECH_STATUS_GREASE_ECH
3081-
and use_retry_config):
3082-
return connect_with_ech(hostname, [ssock.get_ech_retry_config()],
3083-
False)
3084-
return ssock
3123+
else:
3124+
context.options |= ssl.OP_ECH_GREASE
3125+
sock = socket.create_connection((hostname, 443))
3126+
try:
3127+
ssock = context.wrap_socket(sock, server_hostname=hostname)
3128+
except BaseException:
3129+
sock.close()
3130+
raise
3131+
if (use_retry_config
3132+
and ssock.get_ech_status()
3133+
== ssl.ECHStatus.ECH_STATUS_GREASE_ECH):
3134+
try:
3135+
retry_config = ssock.get_ech_retry_config()
3136+
finally:
3137+
ssock.close()
3138+
return connect_with_tls_ech(hostname, retry_config, False)
3139+
return ssock
30853140

30863141
hostname = "www.python.org"
3087-
ech_configs = [] # Replace with a call to a function to lookup
3088-
# ECH configurations in DNS
3142+
ech_config = None # Replace with an ECHConfigList discovered through DNS.
30893143

3090-
ssock = connect_with_tls_ech(hostname, ech_configs)
3144+
with connect_with_tls_ech(hostname, ech_config) as ssock:
3145+
print(ssock.get_ech_status())
30913146

30923147
The following classes, methods, and attributes will be useful for using ECH:
30933148

30943149
- :class:`ECHStatus`
30953150
- :meth:`SSLContext.set_ech_config`
30963151
- :meth:`SSLContext.set_outer_alpn_protocols`
3097-
- :meth:`SSLContext.set_outer_server_hostname`
30983152
- :meth:`SSLSocket.get_ech_status`
30993153
- :meth:`SSLSocket.get_ech_retry_config`
31003154

‎Doc/whatsnew/3.16.rst‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -688,6 +688,18 @@ sqlite3
688688
ssl
689689
---
690690

691+
* Added support for :ref:`Encrypted Client Hello <ssl-encrypted-client-hello>`
692+
(ECH) with OpenSSL 4.0 or later. Clients can use
693+
:meth:`~ssl.SSLContext.set_ech_config` to supply an ECHConfigList discovered
694+
through DNS, for example using a third-party library such as ``dnspython``
695+
to query HTTPS or SVCB records. Clients can also enable
696+
:data:`~ssl.OP_ECH_GREASE` and retrieve a server's retry configuration
697+
with :meth:`~ssl.SSLSocket.get_ech_retry_config` for a subsequent connection.
698+
:data:`~ssl.HAS_ECH` indicates whether ECH is available, and
699+
:meth:`~ssl.SSLSocket.get_ech_status` reports the status as an
700+
:class:`~ssl.ECHStatus` member.
701+
(Contributed by Iain R. Learmonth and Peter Bierma in :gh:`89730`.)
702+
691703
* :const:`ssl.OPENSSL_VERSION_INFO` is now a named tuple.
692704
Added :const:`~ssl.OPENSSL_API_VERSION_INFO` which provides information
693705
about the version of the OpenSSL library that was used for building

‎Lib/ssl.py‎

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,8 @@
116116

117117
from _ssl import (
118118
HAS_SNI, HAS_ECDH, HAS_NPN, HAS_ALPN, HAS_SSLv2, HAS_SSLv3, HAS_TLSv1,
119-
HAS_TLSv1_1, HAS_TLSv1_2, HAS_TLSv1_3, HAS_PSK, HAS_PSK_TLS13, HAS_PHA
119+
HAS_TLSv1_1, HAS_TLSv1_2, HAS_TLSv1_3, HAS_PSK, HAS_PSK_TLS13, HAS_PHA,
120+
HAS_ECH
120121
)
121122
from _ssl import _DEFAULT_CIPHERS, _OPENSSL_API_VERSION
122123

@@ -522,11 +523,11 @@ def shim_cb(sslobj, servername, sslctx):
522523
self.sni_callback = shim_cb
523524

524525
def set_alpn_protocols(self, alpn_protocols):
525-
protos = encode_alpn_protocol_list(alpn_protocols)
526+
protos = _encode_alpn_protocol_list(alpn_protocols)
526527
self._set_alpn_protocols(protos)
527528

528529
def set_outer_alpn_protocols(self, alpn_protocols):
529-
protos = encode_alpn_protocol_list(alpn_protocols)
530+
protos = _encode_alpn_protocol_list(alpn_protocols)
530531
self._set_outer_alpn_protocols(protos)
531532

532533
def _load_windows_store_certs(self, storename, purpose):
@@ -1003,8 +1004,13 @@ def verify_client_post_handshake(self):
10031004
return self._sslobj.verify_client_post_handshake()
10041005

10051006
def get_ech_status(self):
1007+
"""Return the status of Encrypted Client Hello processing."""
10061008
return ECHStatus(self._sslobj.get_ech_status()[0])
10071009

1010+
def get_ech_retry_config(self):
1011+
"""Return the server's ECH retry configuration as bytes."""
1012+
return self._sslobj.get_ech_retry_config()
1013+
10081014

10091015
def _sslcopydoc(func):
10101016
"""Copy docstring from SSLObject to SSLSocket"""
@@ -1031,14 +1037,11 @@ def __init__(self, *args, **kwargs):
10311037
@classmethod
10321038
def _create(cls, sock, server_side=False, do_handshake_on_connect=True,
10331039
suppress_ragged_eofs=True, server_hostname=None,
1034-
context=None, session=None, outer_server_hostname=None):
1040+
context=None, session=None):
10351041
if sock.getsockopt(SOL_SOCKET, SO_TYPE) != SOCK_STREAM:
10361042
raise NotImplementedError("only stream sockets are supported")
10371043
_check_sslobject_params(server_side=server_side, context=context,
10381044
server_hostname=server_hostname, session=session)
1039-
if server_side and outer_server_hostname:
1040-
raise ValueError("outer_server_hostname can only be specified "
1041-
"in client mode")
10421045

10431046
sock_timeout = sock.gettimeout()
10441047
kwargs = dict(
@@ -1137,7 +1140,8 @@ def context(self, ctx):
11371140
@property
11381141
def outer_server_hostname(self):
11391142
"""The server name used in the outer ClientHello."""
1140-
return self._sslobj.get_ech_status()[2]
1143+
if self._sslobj is not None:
1144+
return self._sslobj.get_ech_status()[2]
11411145

11421146
@property
11431147
@_sslcopydoc
@@ -1440,8 +1444,15 @@ def verify_client_post_handshake(self):
14401444
else:
14411445
raise ValueError("No SSL wrapper around " + str(self))
14421446

1447+
@_sslcopydoc
14431448
def get_ech_status(self):
1444-
return ECHStatus(self._sslobj.get_ech_status()[0])
1449+
if self._sslobj is not None:
1450+
return ECHStatus(self._sslobj.get_ech_status()[0])
1451+
1452+
@_sslcopydoc
1453+
def get_ech_retry_config(self):
1454+
if self._sslobj is not None:
1455+
return self._sslobj.get_ech_retry_config()
14451456

14461457
def _real_close(self):
14471458
self._sslobj = None
@@ -1610,7 +1621,8 @@ def get_server_certificate(addr, ssl_version=PROTOCOL_TLS_CLIENT,
16101621
def get_protocol_name(protocol_code):
16111622
return _PROTOCOL_NAMES.get(protocol_code, '<unknown>')
16121623

1613-
def encode_alpn_protocol_list(alpn_protocols):
1624+
1625+
def _encode_alpn_protocol_list(alpn_protocols):
16141626
protos = bytearray()
16151627
for protocol in alpn_protocols:
16161628
b = bytes(protocol, 'ascii')

0 commit comments

Comments
 (0)