@@ -753,14 +753,16 @@ def test_create_connection_ssl_failed_certificate(self):
753753 sslctx = test_utils .simple_server_sslcontext ()
754754 client_sslctx = test_utils .simple_client_sslcontext (
755755 disable_verify = False )
756+ server_err = None
756757
757758 def server (sock ):
759+ nonlocal server_err
758760 try :
759761 sock .start_tls (
760762 sslctx ,
761763 server_side = True )
762- except ssl .SSLError :
763- pass
764+ except ssl .SSLError as exc :
765+ server_err = exc
764766 except OSError :
765767 pass
766768 finally :
@@ -780,6 +782,62 @@ async def client(addr):
780782 with self .assertRaises (ssl .SSLCertVerificationError ):
781783 self .loop .run_until_complete (client (srv .addr ))
782784
785+ # gh-98078: the client must send a fatal TLS alert to the
786+ # server instead of just closing the connection, so that the
787+ # server knows why the handshake failed.
788+ self .assertIsInstance (server_err , ssl .SSLError )
789+ self .assertIn ('ALERT_UNKNOWN_CA' , server_err .reason or '' )
790+
791+ def test_create_server_ssl_failed_handshake_sends_alert (self ):
792+ # gh-98078: when the handshake fails, the server must send the
793+ # fatal TLS alert generated by OpenSSL to the client before
794+ # closing the connection, so that the client knows why the
795+ # handshake failed (here: no TLS version in common).
796+ if not ssl .HAS_TLSv1_3 or not ssl .HAS_TLSv1_2 :
797+ self .skipTest ('needs TLSv1.2 and TLSv1.3 support' )
798+
799+ self .loop .set_exception_handler (lambda loop , ctx : None )
800+
801+ server_context = test_utils .simple_server_sslcontext ()
802+ server_context .minimum_version = ssl .TLSVersion .TLSv1_3
803+ client_context = test_utils .simple_client_sslcontext ()
804+ client_context .maximum_version = ssl .TLSVersion .TLSv1_2
805+
806+ client_done = self .loop .create_future ()
807+ client_err = None
808+
809+ def client (sock , addr ):
810+ nonlocal client_err
811+ try :
812+ sock .settimeout (self .TIMEOUT )
813+ sock .connect (addr )
814+ try :
815+ sock .start_tls (client_context )
816+ except OSError as exc :
817+ client_err = exc
818+ finally :
819+ sock .close ()
820+ finally :
821+ self .loop .call_soon_threadsafe (
822+ client_done .set_result , None )
823+
824+ async def run_main ():
825+ server = await self .loop .create_server (
826+ asyncio .Protocol , '127.0.0.1' , 0 , ssl = server_context )
827+ addr = server .sockets [0 ].getsockname ()
828+ try :
829+ with self .tcp_client (lambda sock : client (sock , addr ),
830+ timeout = self .TIMEOUT ):
831+ await asyncio .wait_for (client_done , self .TIMEOUT )
832+ finally :
833+ server .close ()
834+ await server .wait_closed ()
835+
836+ self .loop .run_until_complete (run_main ())
837+
838+ self .assertIsInstance (client_err , ssl .SSLError )
839+ self .assertIn ('ALERT_PROTOCOL_VERSION' , client_err .reason or '' )
840+
783841 def test_start_tls_client_corrupted_ssl (self ):
784842 self .loop .set_exception_handler (lambda loop , ctx : None )
785843
0 commit comments