@@ -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