@@ -399,6 +399,34 @@ def do_ssl_object_handshake(sslobject, outgoing, max_retry=25):
399399 return data
400400
401401
402+ def connected_bio_pair (client_context , server_context , hostname , max_retry = 5 ):
403+ """Handshake a client and a server SSLObject against each other.
404+
405+ Everything happens in memory, so this needs no socket and no thread.
406+ Returns the two objects followed by their four BIOs, in the order
407+ client, server, c_in, c_out, s_in, s_out.
408+ """
409+ c_in , c_out = ssl .MemoryBIO (), ssl .MemoryBIO ()
410+ s_in , s_out = ssl .MemoryBIO (), ssl .MemoryBIO ()
411+ client = client_context .wrap_bio (c_in , c_out , server_hostname = hostname )
412+ server = server_context .wrap_bio (s_in , s_out , server_side = True )
413+
414+ # Loop on the handshake for a bit to get it settled
415+ for _ in range (max_retry ):
416+ with contextlib .suppress (ssl .SSLWantReadError ):
417+ client .do_handshake ()
418+ if c_out .pending :
419+ s_in .write (c_out .read ())
420+ with contextlib .suppress (ssl .SSLWantReadError ):
421+ server .do_handshake ()
422+ if s_out .pending :
423+ c_in .write (s_out .read ())
424+ # Now the handshakes should be complete (don't raise WantReadError)
425+ client .do_handshake ()
426+ server .do_handshake ()
427+ return client , server , c_in , c_out , s_in , s_out
428+
429+
402430class BasicSocketTests (unittest .TestCase ):
403431
404432 def test_constants (self ):
@@ -1859,6 +1887,7 @@ def test__create_stdlib_context_check_hostname(self):
18591887 def test_delete_sslobject_attributes (self ):
18601888 # None of the attributes of _ssl._SSLSocket can be deleted.
18611889 ctx = ssl .SSLContext (ssl .PROTOCOL_TLS_CLIENT )
1890+ ctx .check_hostname = False
18621891 sslobj = ctx .wrap_bio (ssl .MemoryBIO (), ssl .MemoryBIO ())._sslobj
18631892 for name in 'context' , 'owner' , 'session' , 'session_reused' :
18641893 with self .subTest (name = name ):
@@ -2053,6 +2082,10 @@ def test_subclass(self):
20532082
20542083 def test_bad_server_hostname (self ):
20552084 ctx = ssl .create_default_context ()
2085+ # Omitting the name entirely is bad too: this context checks it.
2086+ with self .assertRaises (ValueError ):
2087+ ctx .wrap_bio (ssl .MemoryBIO (), ssl .MemoryBIO (),
2088+ server_hostname = None )
20562089 with self .assertRaises (ValueError ):
20572090 ctx .wrap_bio (ssl .MemoryBIO (), ssl .MemoryBIO (),
20582091 server_hostname = "" )
@@ -2137,6 +2170,64 @@ def test_private_init(self):
21372170 with self .assertRaisesRegex (TypeError , "public constructor" ):
21382171 ssl .SSLObject (bio , bio )
21392172
2173+ def test_check_hostname_requires_server_hostname (self ):
2174+ # wrap_bio() used to accept a context asking for hostname checking
2175+ # without a name to check against, and then verify the certificate
2176+ # chain but never the peer's identity, with check_hostname still
2177+ # reporting True and nothing reporting the check had been skipped.
2178+ # It must refuse that call, as wrap_socket() already did.
2179+ client_context , _ , hostname = testing_context ()
2180+ self .assertTrue (client_context .check_hostname )
2181+
2182+ for server_hostname in (None , "" ):
2183+ with self .subTest (server_hostname = server_hostname ):
2184+ with self .assertRaisesRegex (
2185+ ValueError ,
2186+ "check_hostname requires server_hostname" ):
2187+ client_context .wrap_bio (ssl .MemoryBIO (), ssl .MemoryBIO (),
2188+ server_hostname = server_hostname )
2189+ # The sibling constructor refuses the very same call.
2190+ with socket .socket () as sock :
2191+ with self .assertRaisesRegex (
2192+ ValueError ,
2193+ "check_hostname requires server_hostname" ):
2194+ client_context .wrap_socket (
2195+ sock , server_hostname = server_hostname )
2196+
2197+ # A name was all that was missing.
2198+ client_context .wrap_bio (ssl .MemoryBIO (), ssl .MemoryBIO (),
2199+ server_hostname = hostname )
2200+
2201+ # Asking for no hostname check remains a way to say so explicitly.
2202+ context = make_test_context ()
2203+ self .assertFalse (context .check_hostname )
2204+ context .wrap_bio (ssl .MemoryBIO (), ssl .MemoryBIO ())
2205+
2206+ def test_server_side_bad_params (self ):
2207+ # A server neither sends a hostname nor resumes a client's session,
2208+ # so wrap_bio() rejects both in server mode like wrap_socket()
2209+ client_context , server_context , hostname = testing_context ()
2210+
2211+ with self .assertRaisesRegex (
2212+ ValueError ,
2213+ "server_hostname can only be specified in client mode" ):
2214+ server_context .wrap_bio (ssl .MemoryBIO (), ssl .MemoryBIO (),
2215+ server_side = True ,
2216+ server_hostname = hostname )
2217+
2218+ client , server , * _ = connected_bio_pair (
2219+ client_context , server_context , hostname )
2220+ session = client .session
2221+ self .assertIsNotNone (session )
2222+ with self .assertRaisesRegex (
2223+ ValueError , "session can only be specified in client mode" ):
2224+ server_context .wrap_bio (ssl .MemoryBIO (), ssl .MemoryBIO (),
2225+ server_side = True , session = session )
2226+
2227+ # Neither argument is what a server passes, so this still works.
2228+ server_context .wrap_bio (ssl .MemoryBIO (), ssl .MemoryBIO (),
2229+ server_side = True )
2230+
21402231 def test_unwrap (self ):
21412232 client_ctx , server_ctx , hostname = testing_context ()
21422233 c_in = ssl .MemoryBIO ()
0 commit comments