@@ -384,6 +384,34 @@ def do_ssl_object_handshake(sslobject, outgoing, max_retry=25):
384384 return data
385385
386386
387+ def connected_bio_pair (client_context , server_context , hostname , max_retry = 5 ):
388+ """Handshake a client and a server SSLObject against each other.
389+
390+ Everything happens in memory, so this needs no socket and no thread.
391+ Returns the two objects followed by their four BIOs, in the order
392+ client, server, c_in, c_out, s_in, s_out.
393+ """
394+ c_in , c_out = ssl .MemoryBIO (), ssl .MemoryBIO ()
395+ s_in , s_out = ssl .MemoryBIO (), ssl .MemoryBIO ()
396+ client = client_context .wrap_bio (c_in , c_out , server_hostname = hostname )
397+ server = server_context .wrap_bio (s_in , s_out , server_side = True )
398+
399+ # Loop on the handshake for a bit to get it settled
400+ for _ in range (max_retry ):
401+ with contextlib .suppress (ssl .SSLWantReadError ):
402+ client .do_handshake ()
403+ if c_out .pending :
404+ s_in .write (c_out .read ())
405+ with contextlib .suppress (ssl .SSLWantReadError ):
406+ server .do_handshake ()
407+ if s_out .pending :
408+ c_in .write (s_out .read ())
409+ # Now the handshakes should be complete (don't raise WantReadError)
410+ client .do_handshake ()
411+ server .do_handshake ()
412+ return client , server , c_in , c_out , s_in , s_out
413+
414+
387415class BasicSocketTests (unittest .TestCase ):
388416
389417 def test_constants (self ):
@@ -1776,6 +1804,7 @@ def test__create_stdlib_context_check_hostname(self):
17761804 def test_delete_sslobject_attributes (self ):
17771805 # None of the attributes of _ssl._SSLSocket can be deleted.
17781806 ctx = ssl .SSLContext (ssl .PROTOCOL_TLS_CLIENT )
1807+ ctx .check_hostname = False
17791808 sslobj = ctx .wrap_bio (ssl .MemoryBIO (), ssl .MemoryBIO ())._sslobj
17801809 for name in 'context' , 'owner' , 'session' , 'session_reused' :
17811810 with self .subTest (name = name ):
@@ -1970,6 +1999,10 @@ def test_subclass(self):
19701999
19712000 def test_bad_server_hostname (self ):
19722001 ctx = ssl .create_default_context ()
2002+ # Omitting the name entirely is bad too: this context checks it.
2003+ with self .assertRaises (ValueError ):
2004+ ctx .wrap_bio (ssl .MemoryBIO (), ssl .MemoryBIO (),
2005+ server_hostname = None )
19732006 with self .assertRaises (ValueError ):
19742007 ctx .wrap_bio (ssl .MemoryBIO (), ssl .MemoryBIO (),
19752008 server_hostname = "" )
@@ -2054,6 +2087,64 @@ def test_private_init(self):
20542087 with self .assertRaisesRegex (TypeError , "public constructor" ):
20552088 ssl .SSLObject (bio , bio )
20562089
2090+ def test_check_hostname_requires_server_hostname (self ):
2091+ # wrap_bio() used to accept a context asking for hostname checking
2092+ # without a name to check against, and then verify the certificate
2093+ # chain but never the peer's identity, with check_hostname still
2094+ # reporting True and nothing reporting the check had been skipped.
2095+ # It must refuse that call, as wrap_socket() already did.
2096+ client_context , _ , hostname = testing_context ()
2097+ self .assertTrue (client_context .check_hostname )
2098+
2099+ for server_hostname in (None , "" ):
2100+ with self .subTest (server_hostname = server_hostname ):
2101+ with self .assertRaisesRegex (
2102+ ValueError ,
2103+ "check_hostname requires server_hostname" ):
2104+ client_context .wrap_bio (ssl .MemoryBIO (), ssl .MemoryBIO (),
2105+ server_hostname = server_hostname )
2106+ # The sibling constructor refuses the very same call.
2107+ with socket .socket () as sock :
2108+ with self .assertRaisesRegex (
2109+ ValueError ,
2110+ "check_hostname requires server_hostname" ):
2111+ client_context .wrap_socket (
2112+ sock , server_hostname = server_hostname )
2113+
2114+ # A name was all that was missing.
2115+ client_context .wrap_bio (ssl .MemoryBIO (), ssl .MemoryBIO (),
2116+ server_hostname = hostname )
2117+
2118+ # Asking for no hostname check remains a way to say so explicitly.
2119+ context = make_test_context ()
2120+ self .assertFalse (context .check_hostname )
2121+ context .wrap_bio (ssl .MemoryBIO (), ssl .MemoryBIO ())
2122+
2123+ def test_server_side_bad_params (self ):
2124+ # A server neither sends a hostname nor resumes a client's session,
2125+ # so wrap_bio() rejects both in server mode like wrap_socket()
2126+ client_context , server_context , hostname = testing_context ()
2127+
2128+ with self .assertRaisesRegex (
2129+ ValueError ,
2130+ "server_hostname can only be specified in client mode" ):
2131+ server_context .wrap_bio (ssl .MemoryBIO (), ssl .MemoryBIO (),
2132+ server_side = True ,
2133+ server_hostname = hostname )
2134+
2135+ client , server , * _ = connected_bio_pair (
2136+ client_context , server_context , hostname )
2137+ session = client .session
2138+ self .assertIsNotNone (session )
2139+ with self .assertRaisesRegex (
2140+ ValueError , "session can only be specified in client mode" ):
2141+ server_context .wrap_bio (ssl .MemoryBIO (), ssl .MemoryBIO (),
2142+ server_side = True , session = session )
2143+
2144+ # Neither argument is what a server passes, so this still works.
2145+ server_context .wrap_bio (ssl .MemoryBIO (), ssl .MemoryBIO (),
2146+ server_side = True )
2147+
20572148 def test_unwrap (self ):
20582149 client_ctx , server_ctx , hostname = testing_context ()
20592150 c_in = ssl .MemoryBIO ()
0 commit comments