@@ -412,6 +412,34 @@ def do_ssl_object_handshake(sslobject, outgoing, max_retry=25):
412412 return data
413413
414414
415+ def connected_bio_pair (client_context , server_context , hostname , max_retry = 5 ):
416+ """Handshake a client and a server SSLObject against each other.
417+
418+ Everything happens in memory, so this needs no socket and no thread.
419+ Returns the two objects followed by their four BIOs, in the order
420+ client, server, c_in, c_out, s_in, s_out.
421+ """
422+ c_in , c_out = ssl .MemoryBIO (), ssl .MemoryBIO ()
423+ s_in , s_out = ssl .MemoryBIO (), ssl .MemoryBIO ()
424+ client = client_context .wrap_bio (c_in , c_out , server_hostname = hostname )
425+ server = server_context .wrap_bio (s_in , s_out , server_side = True )
426+
427+ # Loop on the handshake for a bit to get it settled
428+ for _ in range (max_retry ):
429+ with contextlib .suppress (ssl .SSLWantReadError ):
430+ client .do_handshake ()
431+ if c_out .pending :
432+ s_in .write (c_out .read ())
433+ with contextlib .suppress (ssl .SSLWantReadError ):
434+ server .do_handshake ()
435+ if s_out .pending :
436+ c_in .write (s_out .read ())
437+ # Now the handshakes should be complete (don't raise WantReadError)
438+ client .do_handshake ()
439+ server .do_handshake ()
440+ return client , server , c_in , c_out , s_in , s_out
441+
442+
415443class BasicSocketTests (unittest .TestCase ):
416444
417445 def test_constants (self ):
@@ -1747,6 +1775,7 @@ def test__create_stdlib_context_check_hostname(self):
17471775 def test_delete_sslobject_attributes (self ):
17481776 # None of the attributes of _ssl._SSLSocket can be deleted.
17491777 ctx = ssl .SSLContext (ssl .PROTOCOL_TLS_CLIENT )
1778+ ctx .check_hostname = False
17501779 sslobj = ctx .wrap_bio (ssl .MemoryBIO (), ssl .MemoryBIO ())._sslobj
17511780 for name in 'context' , 'owner' , 'session' , 'session_reused' :
17521781 with self .subTest (name = name ):
@@ -1941,6 +1970,10 @@ def test_subclass(self):
19411970
19421971 def test_bad_server_hostname (self ):
19431972 ctx = ssl .create_default_context ()
1973+ # Omitting the name entirely is bad too: this context checks it.
1974+ with self .assertRaises (ValueError ):
1975+ ctx .wrap_bio (ssl .MemoryBIO (), ssl .MemoryBIO (),
1976+ server_hostname = None )
19441977 with self .assertRaises (ValueError ):
19451978 ctx .wrap_bio (ssl .MemoryBIO (), ssl .MemoryBIO (),
19461979 server_hostname = "" )
@@ -2025,6 +2058,64 @@ def test_private_init(self):
20252058 with self .assertRaisesRegex (TypeError , "public constructor" ):
20262059 ssl .SSLObject (bio , bio )
20272060
2061+ def test_check_hostname_requires_server_hostname (self ):
2062+ # wrap_bio() used to accept a context asking for hostname checking
2063+ # without a name to check against, and then verify the certificate
2064+ # chain but never the peer's identity, with check_hostname still
2065+ # reporting True and nothing reporting the check had been skipped.
2066+ # It must refuse that call, as wrap_socket() already did.
2067+ client_context , _ , hostname = testing_context ()
2068+ self .assertTrue (client_context .check_hostname )
2069+
2070+ for server_hostname in (None , "" ):
2071+ with self .subTest (server_hostname = server_hostname ):
2072+ with self .assertRaisesRegex (
2073+ ValueError ,
2074+ "check_hostname requires server_hostname" ):
2075+ client_context .wrap_bio (ssl .MemoryBIO (), ssl .MemoryBIO (),
2076+ server_hostname = server_hostname )
2077+ # The sibling constructor refuses the very same call.
2078+ with socket .socket () as sock :
2079+ with self .assertRaisesRegex (
2080+ ValueError ,
2081+ "check_hostname requires server_hostname" ):
2082+ client_context .wrap_socket (
2083+ sock , server_hostname = server_hostname )
2084+
2085+ # A name was all that was missing.
2086+ client_context .wrap_bio (ssl .MemoryBIO (), ssl .MemoryBIO (),
2087+ server_hostname = hostname )
2088+
2089+ # Asking for no hostname check remains a way to say so explicitly.
2090+ context = make_test_context ()
2091+ self .assertFalse (context .check_hostname )
2092+ context .wrap_bio (ssl .MemoryBIO (), ssl .MemoryBIO ())
2093+
2094+ def test_server_side_bad_params (self ):
2095+ # A server neither sends a hostname nor resumes a client's session,
2096+ # so wrap_bio() rejects both in server mode like wrap_socket()
2097+ client_context , server_context , hostname = testing_context ()
2098+
2099+ with self .assertRaisesRegex (
2100+ ValueError ,
2101+ "server_hostname can only be specified in client mode" ):
2102+ server_context .wrap_bio (ssl .MemoryBIO (), ssl .MemoryBIO (),
2103+ server_side = True ,
2104+ server_hostname = hostname )
2105+
2106+ client , server , * _ = connected_bio_pair (
2107+ client_context , server_context , hostname )
2108+ session = client .session
2109+ self .assertIsNotNone (session )
2110+ with self .assertRaisesRegex (
2111+ ValueError , "session can only be specified in client mode" ):
2112+ server_context .wrap_bio (ssl .MemoryBIO (), ssl .MemoryBIO (),
2113+ server_side = True , session = session )
2114+
2115+ # Neither argument is what a server passes, so this still works.
2116+ server_context .wrap_bio (ssl .MemoryBIO (), ssl .MemoryBIO (),
2117+ server_side = True )
2118+
20282119 def test_unwrap (self ):
20292120 client_ctx , server_ctx , hostname = testing_context ()
20302121 c_in = ssl .MemoryBIO ()
0 commit comments