@@ -332,8 +332,8 @@ Exceptions
332332
333333 .. attribute :: retry_config
334334
335- The server's ECH retry configuration as a binary-encoded ECHConfigList,
336- or empty :class: ` bytes ` if the server did not provide one or the server
335+ The server's ECH retry configuration as an ECHConfigList,
336+ or `` b"" ` ` if the server did not provide one or the server
337337 could not be authenticated under the outer server name.
338338
339339 .. versionadded :: next
@@ -1443,14 +1443,8 @@ SSL sockets also have the following additional methods and attributes:
14431443
14441444.. method :: SSLSocket.get_ech_retry_config()
14451445
1446- Return the server's ECH retry configuration as :class: `bytes `, or empty
1447- bytes if no retry configuration is available. When the status returned by
1448- :meth: `SSLSocket.get_ech_status ` after the handshake is
1449- :attr: `ECHStatus.ECH_STATUS_GREASE_ECH `, the configuration can be used
1450- for a new connection using ECH. When the server rejects a real ECH
1451- attempt, the handshake raises :exc: `SSLECHRejectedError `, which carries
1452- the same configuration in its :attr: `~SSLECHRejectedError.retry_config `
1453- attribute.
1446+ Return the server's ECH retry configuration as :class: `bytes `, or ``b"" ``
1447+ if no retry configuration is available.
14541448
14551449 Only available when :data: `HAS_ECH ` is ``True ``.
14561450
@@ -1470,16 +1464,11 @@ SSL sockets also have the following additional methods and attributes:
14701464 Set the Encrypted Client Hello (ECH) configuration for this connection
14711465 only, replacing any configuration set on the context with
14721466 :meth: `SSLContext.set_ech_config `. The *ech_config * parameter accepts the
1473- same values. Unlike the context's configuration, which applies to every
1474- connection made from it, a connection's configuration is only used by that
1475- connection and can therefore be chosen per destination host, which is how
1476- ECH configurations are published in DNS.
1467+ same values.
14771468
14781469 The configuration must be set before the handshake starts, so this is
14791470 mostly useful with ``do_handshake_on_connect=False `` or on a socket that
1480- is not connected yet. :meth: `SSLContext.wrap_socket ` and
1481- :meth: `SSLContext.wrap_bio ` accept the configuration as their *ech_config *
1482- argument instead.
1471+ is not connected yet.
14831472
14841473 Only available when :data: `HAS_ECH ` is ``True ``.
14851474
@@ -1560,8 +1549,8 @@ SSL sockets also have the following additional methods and attributes:
15601549.. attribute :: SSLSocket.outer_server_hostname
15611550
15621551 Hostname of the server name used in the outer ClientHello when Encrypted Client
1563- Hello (ECH) is used: :class: ` str ` type , or ``None `` for server-side socket or
1564- if no outer server name is available.
1552+ Hello (ECH) is used, or ``None `` for server-side socket or if no outer
1553+ server name is available.
15651554
15661555 Only available when :data: `HAS_ECH ` is ``True ``.
15671556
@@ -1753,25 +1742,16 @@ to speed up repeated connections from the same clients.
17531742
17541743 Load a server's Encrypted Client Hello (ECH) private key and configuration.
17551744 The *keyfile * must be the path to a PEM file containing a private key
1756- followed by its matching ECHConfigList. Load the server's TLS
1757- certificate and private key separately with :meth: `load_cert_chain `.
1745+ followed by its matching ECHConfigList.
17581746
17591747 The *password * argument is used to decrypt encrypted private keys. It accepts
17601748 a string, bytes, bytearray, or a callable returning one of those types, as in
1761- :meth: `load_cert_chain `. Passwords are never requested interactively.
1749+ :meth: `load_cert_chain `.
17621750
17631751 Each call adds keys and configurations to those already loaded into the
17641752 context. If *for_retry * is true, the new configurations are included in
17651753 ECH retry configurations sent to clients.
17661754
1767- Configure ECH keys on the initial server context before accepting
1768- connections. After successful ECH decryption, :attr: `sni_callback `
1769- receives the inner server name.
1770-
1771- Invalid or missing keys or configurations, or a key that does not match
1772- its configuration, raise :exc: `SSLError `. File access errors raise
1773- :exc: `OSError `.
1774-
17751755 Only available when :data: `HAS_ECH ` is ``True ``.
17761756
17771757 .. versionadded :: next
@@ -1974,7 +1954,6 @@ to speed up repeated connections from the same clients.
19741954
19751955 Sets an Encrypted Client Hello (ECH) configuration, which may be discovered from
19761956 an HTTPS resource record in DNS or from :meth: `SSLSocket.get_ech_retry_config `.
1977- Each call replaces the context's previous configuration.
19781957
19791958 The configuration applies to every connection made from the context, and a
19801959 server that cannot decrypt it rejects the connection with
@@ -1984,7 +1963,7 @@ to speed up repeated connections from the same clients.
19841963 pass it as the *ech_config * argument of :meth: `wrap_socket ` or
19851964 :meth: `wrap_bio ` instead.
19861965
1987- If the input value provided contains no suitable value (e.g. if it only contains
1966+ If the input value provided contains no suitable value (such as if it only contains
19881967 ECH configuration versions that are not supported), an :class: `SSLError ` will be
19891968 raised.
19901969
@@ -3207,31 +3186,52 @@ ECHConfigList discovered through DNS and retries once with the server's retry
32073186configuration. If no configuration is supplied, it enables ECH GREASE, which a
32083187server may answer with a retry configuration for a later connection::
32093188
3210- import socket
3211- import ssl
3189+ import http.client
3190+ import socket
3191+ import ssl
32123192
32133193
3214- def connect_with_tls_ech(hostname, ech_config=None, retry=True):
3215- context = ssl.create_default_context()
3216- if ech_config is None:
3217- context.options |= ssl.OP_ECH_GREASE
3218- sock = socket.create_connection((hostname, 443))
3219- try:
3220- return context.wrap_socket(sock, server_hostname=hostname,
3221- ech_config=ech_config)
3222- except ssl.SSLECHRejectedError as exc:
3223- if retry and exc.retry_config:
3224- return connect_with_tls_ech(hostname, exc.retry_config,
3225- retry=False)
3226- raise
3227-
3228- hostname = "www.python.org"
3229- ech_config = None # Replace with an ECHConfigList discovered through DNS.
3230-
3231- with connect_with_tls_ech(hostname, ech_config) as ssock:
3232- print(ssock.get_ech_status())
3233- if ssock.get_ech_status() == ssl.ECHStatus.ECH_STATUS_GREASE_ECH:
3234- print("retry configuration:", ssock.get_ech_retry_config())
3194+ def connect_with_tls_ech(hostname, ech_config=None, use_retry_config=True):
3195+ context = ssl.create_default_context()
3196+ if ech_config is not None:
3197+ context.set_ech_config(ech_config)
3198+ else:
3199+ context.options |= ssl.OP_ECH_GREASE
3200+ sock = socket.create_connection((hostname, 443), timeout=10)
3201+ try:
3202+ ssock = context.wrap_socket(sock, server_hostname=hostname)
3203+ except BaseException:
3204+ sock.close()
3205+ raise
3206+ if (use_retry_config
3207+ and ssock.get_ech_status()
3208+ == ssl.ECHStatus.ECH_STATUS_GREASE_ECH):
3209+ try:
3210+ retry_config = ssock.get_ech_retry_config()
3211+ finally:
3212+ ssock.close()
3213+ return connect_with_tls_ech(hostname, retry_config, False)
3214+ return ssock
3215+
3216+ hostname = "python.org"
3217+ ech_config = None # Replace with an ECHConfigList discovered through DNS.
3218+
3219+ with connect_with_tls_ech(hostname, ech_config) as ssock:
3220+ print("ECH status:", ssock.get_ech_status().name)
3221+ request = (
3222+ "GET / HTTP/1.1\r\n"
3223+ f"Host: {hostname}\r\n"
3224+ "Connection: close\r\n\r\n"
3225+ )
3226+ ssock.sendall(request.encode("ascii"))
3227+ with http.client.HTTPResponse(ssock) as response:
3228+ response.begin()
3229+ print(f"HTTP {response.status} {response.reason}")
3230+ for name, value in response.getheaders():
3231+ print(f"{name}: {value}")
3232+ print()
3233+ print(response.read().decode("utf-8", errors="replace"))
3234+
32353235
32363236The following classes, methods, and attributes will be useful for using ECH:
32373237
0 commit comments