107107from _ssl import _SSLContext , MemoryBIO , SSLSession
108108from _ssl import (
109109 SSLError , SSLZeroReturnError , SSLWantReadError , SSLWantWriteError ,
110- SSLSyscallError , SSLEOFError , SSLCertVerificationError
110+ SSLSyscallError , SSLEOFError , SSLCertVerificationError ,
111+ SSLECHRejectedError
111112 )
112113from _ssl import txt2obj as _txt2obj , nid2obj as _nid2obj
113114from _ssl import RAND_status , RAND_add , RAND_bytes
116117
117118from _ssl import (
118119 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
120+ HAS_TLSv1_1 , HAS_TLSv1_2 , HAS_TLSv1_3 , HAS_PSK , HAS_PSK_TLS13 , HAS_PHA ,
121+ HAS_ECH
120122)
121123from _ssl import _DEFAULT_CIPHERS , _OPENSSL_API_VERSION
122124
150152 lambda name : name .startswith ('CERT_' ),
151153 source = _ssl )
152154
155+ if HAS_ECH :
156+ _IntEnum ._convert_ (
157+ 'ECHStatus' , __name__ ,
158+ lambda name : name .startswith ('ECH_STATUS_' ),
159+ source = _ssl )
160+
153161PROTOCOL_SSLv23 = _SSLMethod .PROTOCOL_SSLv23 = _SSLMethod .PROTOCOL_TLS
154162_PROTOCOL_NAMES = frozendict ({
155163 value : name for name , value in _SSLMethod .__members__ .items ()})
@@ -374,7 +382,8 @@ def _ipaddress_match(cert_ipaddress, host_ip):
374382 return ip == host_ip
375383
376384
377- def _check_sslobject_params (server_side , context = None , server_hostname = None , session = None ):
385+ def _check_sslobject_params (server_side , context = None , server_hostname = None ,
386+ session = None , ech_config = None ):
378387 """Raises a ValueError if SSLObject._create() parameters aren't valid.
379388 """
380389 if server_side :
@@ -384,6 +393,11 @@ def _check_sslobject_params(server_side, context=None, server_hostname=None, ses
384393 if session is not None :
385394 raise ValueError ("session can only be specified in "
386395 "client mode" )
396+ if ech_config is not None :
397+ raise ValueError ("ech_config can only be specified in "
398+ "client mode" )
399+ if ech_config is not None and not HAS_ECH :
400+ raise NotImplementedError ("ECH is not supported by the OpenSSL library" )
387401 if context .check_hostname and not server_hostname :
388402 raise ValueError ("check_hostname requires server_hostname" )
389403
@@ -464,7 +478,7 @@ def _encode_hostname(self, hostname):
464478 def wrap_socket (self , sock , server_side = False ,
465479 do_handshake_on_connect = True ,
466480 suppress_ragged_eofs = True ,
467- server_hostname = None , session = None ):
481+ server_hostname = None , session = None , ech_config = None ):
468482 # SSLSocket class handles server_hostname encoding before it calls
469483 # ctx._wrap_socket()
470484 return self .sslsocket_class ._create (
@@ -474,17 +488,18 @@ def wrap_socket(self, sock, server_side=False,
474488 suppress_ragged_eofs = suppress_ragged_eofs ,
475489 server_hostname = server_hostname ,
476490 context = self ,
477- session = session
491+ session = session ,
492+ ech_config = ech_config ,
478493 )
479494
480495 def wrap_bio (self , incoming , outgoing , server_side = False ,
481- server_hostname = None , session = None ):
496+ server_hostname = None , session = None , ech_config = None ):
482497 # Need to encode server_hostname here because _wrap_bio() can only
483498 # handle ASCII str.
484499 return self .sslobject_class ._create (
485500 incoming , outgoing , server_side = server_side ,
486501 server_hostname = self ._encode_hostname (server_hostname ),
487- session = session , context = self ,
502+ session = session , context = self , ech_config = ech_config ,
488503 )
489504
490505 def set_npn_protocols (self , npn_protocols ):
@@ -517,16 +532,14 @@ def shim_cb(sslobj, servername, sslctx):
517532 self .sni_callback = shim_cb
518533
519534 def set_alpn_protocols (self , alpn_protocols ):
520- protos = bytearray ()
521- for protocol in alpn_protocols :
522- b = bytes (protocol , 'ascii' )
523- if len (b ) == 0 or len (b ) > 255 :
524- raise SSLError ('ALPN protocols must be 1 to 255 in length' )
525- protos .append (len (b ))
526- protos .extend (b )
527-
535+ protos = _encode_alpn_protocol_list (alpn_protocols )
528536 self ._set_alpn_protocols (protos )
529537
538+ if HAS_ECH :
539+ def set_outer_alpn_protocols (self , alpn_protocols ):
540+ protos = _encode_alpn_protocol_list (alpn_protocols )
541+ self ._set_outer_alpn_protocols (protos )
542+
530543 def _load_windows_store_certs (self , storename , purpose ):
531544 try :
532545 for cert , encoding , trust in enum_certificates (storename ):
@@ -825,15 +838,19 @@ def __init__(self, *args, **kwargs):
825838
826839 @classmethod
827840 def _create (cls , incoming , outgoing , server_side = False ,
828- server_hostname = None , session = None , context = None ):
841+ server_hostname = None , session = None , context = None ,
842+ ech_config = None ):
829843 _check_sslobject_params (server_side = server_side , context = context ,
830- server_hostname = server_hostname , session = session )
844+ server_hostname = server_hostname ,
845+ session = session , ech_config = ech_config )
831846 self = cls .__new__ (cls )
832847 sslobj = context ._wrap_bio (
833848 incoming , outgoing , server_side = server_side ,
834849 server_hostname = server_hostname ,
835850 owner = self , session = session
836851 )
852+ if ech_config is not None :
853+ sslobj .set_ech_config (ech_config )
837854 self ._sslobj = sslobj
838855 return self
839856
@@ -846,6 +863,12 @@ def context(self):
846863 def context (self , ctx ):
847864 self ._sslobj .context = ctx
848865
866+ if HAS_ECH :
867+ @property
868+ def outer_server_hostname (self ):
869+ """The server name used in the outer ClientHello."""
870+ return self ._sslobj .get_ech_status ()[2 ]
871+
849872 @property
850873 def session (self ):
851874 """The SSLSession for client socket."""
@@ -995,6 +1018,23 @@ def version(self):
9951018 def verify_client_post_handshake (self ):
9961019 return self ._sslobj .verify_client_post_handshake ()
9971020
1021+ if HAS_ECH :
1022+ def set_ech_config (self , ech_config ):
1023+ """Set the ECH configuration for this connection.
1024+
1025+ This replaces any configuration inherited from the context
1026+ and must be called before the handshake starts.
1027+ """
1028+ self ._sslobj .set_ech_config (ech_config )
1029+
1030+ def get_ech_status (self ):
1031+ """Return the status of Encrypted Client Hello processing."""
1032+ return ECHStatus (self ._sslobj .get_ech_status ()[0 ])
1033+
1034+ def get_ech_retry_config (self ):
1035+ """Return the server's ECH retry configuration as bytes."""
1036+ return self ._sslobj .get_ech_retry_config ()
1037+
9981038
9991039def _sslcopydoc (func ):
10001040 """Copy docstring from SSLObject to SSLSocket"""
@@ -1021,11 +1061,12 @@ def __init__(self, *args, **kwargs):
10211061 @classmethod
10221062 def _create (cls , sock , server_side = False , do_handshake_on_connect = True ,
10231063 suppress_ragged_eofs = True , server_hostname = None ,
1024- context = None , session = None ):
1064+ context = None , session = None , ech_config = None ):
10251065 if sock .getsockopt (SOL_SOCKET , SO_TYPE ) != SOCK_STREAM :
10261066 raise NotImplementedError ("only stream sockets are supported" )
10271067 _check_sslobject_params (server_side = server_side , context = context ,
1028- server_hostname = server_hostname , session = session )
1068+ server_hostname = server_hostname ,
1069+ session = session , ech_config = ech_config )
10291070
10301071 sock_timeout = sock .gettimeout ()
10311072 kwargs = dict (
@@ -1039,6 +1080,7 @@ def _create(cls, sock, server_side=False, do_handshake_on_connect=True,
10391080 try :
10401081 self ._context = context
10411082 self ._session = session
1083+ self ._ech_config = ech_config
10421084 self ._closed = False
10431085 self ._sslobj = None
10441086 self .server_side = server_side
@@ -1097,6 +1139,8 @@ def _create(cls, sock, server_side=False, do_handshake_on_connect=True,
10971139 self , server_side , self .server_hostname ,
10981140 owner = self , session = self ._session ,
10991141 )
1142+ if self ._ech_config is not None :
1143+ self ._sslobj .set_ech_config (self ._ech_config )
11001144 if do_handshake_on_connect :
11011145 timeout = self .gettimeout ()
11021146 if timeout == 0.0 :
@@ -1121,6 +1165,13 @@ def context(self, ctx):
11211165 self ._context = ctx
11221166 self ._sslobj .context = ctx
11231167
1168+ if HAS_ECH :
1169+ @property
1170+ def outer_server_hostname (self ):
1171+ """The server name used in the outer ClientHello."""
1172+ if self ._sslobj is not None :
1173+ return self ._sslobj .get_ech_status ()[2 ]
1174+
11241175 @property
11251176 @_sslcopydoc
11261177 def session (self ):
@@ -1422,6 +1473,23 @@ def verify_client_post_handshake(self):
14221473 else :
14231474 raise ValueError ("No SSL wrapper around " + str (self ))
14241475
1476+ if HAS_ECH :
1477+ @_sslcopydoc
1478+ def set_ech_config (self , ech_config ):
1479+ self ._ech_config = ech_config
1480+ if self ._sslobj is not None :
1481+ self ._sslobj .set_ech_config (ech_config )
1482+
1483+ @_sslcopydoc
1484+ def get_ech_status (self ):
1485+ if self ._sslobj is not None :
1486+ return ECHStatus (self ._sslobj .get_ech_status ()[0 ])
1487+
1488+ @_sslcopydoc
1489+ def get_ech_retry_config (self ):
1490+ if self ._sslobj is not None :
1491+ return self ._sslobj .get_ech_retry_config ()
1492+
14251493 def _real_close (self ):
14261494 self ._sslobj = None
14271495 super ()._real_close ()
@@ -1449,6 +1517,8 @@ def _real_connect(self, addr, connect_ex):
14491517 owner = self , session = self ._session
14501518 )
14511519 try :
1520+ if self ._ech_config is not None :
1521+ self ._sslobj .set_ech_config (self ._ech_config )
14521522 if connect_ex :
14531523 rc = super ().connect_ex (addr )
14541524 else :
@@ -1588,3 +1658,14 @@ def get_server_certificate(addr, ssl_version=PROTOCOL_TLS_CLIENT,
15881658
15891659def get_protocol_name (protocol_code ):
15901660 return _PROTOCOL_NAMES .get (protocol_code , '<unknown>' )
1661+
1662+
1663+ def _encode_alpn_protocol_list (alpn_protocols ):
1664+ protos = bytearray ()
1665+ for protocol in alpn_protocols :
1666+ b = bytes (protocol , 'ascii' )
1667+ if not b or len (b ) > 255 :
1668+ raise SSLError ('ALPN protocols must be 1 to 255 in length' )
1669+ protos .append (len (b ))
1670+ protos .extend (b )
1671+ return protos
0 commit comments