@@ -402,6 +402,34 @@ def do_ssl_object_handshake(sslobject, outgoing, max_retry=25):
402402 return data
403403
404404
405+ def connected_bio_pair (client_context , server_context , hostname , max_retry = 5 ):
406+ """Handshake a client and a server SSLObject against each other.
407+
408+ Everything happens in memory, so this needs no socket and no thread.
409+ Returns the two objects followed by their four BIOs, in the order
410+ client, server, c_in, c_out, s_in, s_out.
411+ """
412+ c_in , c_out = ssl .MemoryBIO (), ssl .MemoryBIO ()
413+ s_in , s_out = ssl .MemoryBIO (), ssl .MemoryBIO ()
414+ client = client_context .wrap_bio (c_in , c_out , server_hostname = hostname )
415+ server = server_context .wrap_bio (s_in , s_out , server_side = True )
416+
417+ # Loop on the handshake for a bit to get it settled
418+ for _ in range (max_retry ):
419+ with contextlib .suppress (ssl .SSLWantReadError ):
420+ client .do_handshake ()
421+ if c_out .pending :
422+ s_in .write (c_out .read ())
423+ with contextlib .suppress (ssl .SSLWantReadError ):
424+ server .do_handshake ()
425+ if s_out .pending :
426+ c_in .write (s_out .read ())
427+ # Now the handshakes should be complete (don't raise WantReadError)
428+ client .do_handshake ()
429+ server .do_handshake ()
430+ return client , server , c_in , c_out , s_in , s_out
431+
432+
405433class BasicSocketTests (unittest .TestCase ):
406434
407435 def test_constants (self ):
@@ -1990,6 +2018,10 @@ def test_subclass(self):
19902018
19912019 def test_bad_server_hostname (self ):
19922020 ctx = ssl .create_default_context ()
2021+ # Omitting the name entirely is bad too: this context checks it.
2022+ with self .assertRaises (ValueError ):
2023+ ctx .wrap_bio (ssl .MemoryBIO (), ssl .MemoryBIO (),
2024+ server_hostname = None )
19932025 with self .assertRaises (ValueError ):
19942026 ctx .wrap_bio (ssl .MemoryBIO (), ssl .MemoryBIO (),
19952027 server_hostname = "" )
@@ -2074,6 +2106,64 @@ def test_private_init(self):
20742106 with self .assertRaisesRegex (TypeError , "public constructor" ):
20752107 ssl .SSLObject (bio , bio )
20762108
2109+ def test_check_hostname_requires_server_hostname (self ):
2110+ # wrap_bio() used to accept a context asking for hostname checking
2111+ # without a name to check against, and then verify the certificate
2112+ # chain but never the peer's identity, with check_hostname still
2113+ # reporting True and nothing reporting the check had been skipped.
2114+ # It must refuse that call, as wrap_socket() already did.
2115+ client_context , _ , hostname = testing_context ()
2116+ self .assertTrue (client_context .check_hostname )
2117+
2118+ for server_hostname in (None , "" ):
2119+ with self .subTest (server_hostname = server_hostname ):
2120+ with self .assertRaisesRegex (
2121+ ValueError ,
2122+ "check_hostname requires server_hostname" ):
2123+ client_context .wrap_bio (ssl .MemoryBIO (), ssl .MemoryBIO (),
2124+ server_hostname = server_hostname )
2125+ # The sibling constructor refuses the very same call.
2126+ with socket .socket () as sock :
2127+ with self .assertRaisesRegex (
2128+ ValueError ,
2129+ "check_hostname requires server_hostname" ):
2130+ client_context .wrap_socket (
2131+ sock , server_hostname = server_hostname )
2132+
2133+ # A name was all that was missing.
2134+ client_context .wrap_bio (ssl .MemoryBIO (), ssl .MemoryBIO (),
2135+ server_hostname = hostname )
2136+
2137+ # Asking for no hostname check remains a way to say so explicitly.
2138+ context = make_test_context ()
2139+ self .assertFalse (context .check_hostname )
2140+ context .wrap_bio (ssl .MemoryBIO (), ssl .MemoryBIO ())
2141+
2142+ def test_server_side_bad_params (self ):
2143+ # A server neither sends a hostname nor resumes a client's session,
2144+ # so wrap_bio() rejects both in server mode like wrap_socket()
2145+ client_context , server_context , hostname = testing_context ()
2146+
2147+ with self .assertRaisesRegex (
2148+ ValueError ,
2149+ "server_hostname can only be specified in client mode" ):
2150+ server_context .wrap_bio (ssl .MemoryBIO (), ssl .MemoryBIO (),
2151+ server_side = True ,
2152+ server_hostname = hostname )
2153+
2154+ client , server , * _ = connected_bio_pair (
2155+ client_context , server_context , hostname )
2156+ session = client .session
2157+ self .assertIsNotNone (session )
2158+ with self .assertRaisesRegex (
2159+ ValueError , "session can only be specified in client mode" ):
2160+ server_context .wrap_bio (ssl .MemoryBIO (), ssl .MemoryBIO (),
2161+ server_side = True , session = session )
2162+
2163+ # Neither argument is what a server passes, so this still works.
2164+ server_context .wrap_bio (ssl .MemoryBIO (), ssl .MemoryBIO (),
2165+ server_side = True )
2166+
20772167 def test_unwrap (self ):
20782168 client_ctx , server_ctx , hostname = testing_context ()
20792169 c_in = ssl .MemoryBIO ()
0 commit comments