Skip to content

Commit 4df60f1

Browse files
authored
bpo-31386: Custom wrap_bio and wrap_socket type (#3426)
SSLSocket.wrap_bio() and SSLSocket.wrap_socket() hard-code SSLObject and SSLSocket as return types. In the light of future deprecation of ssl.wrap_socket() module function and direct instantiation of SSLSocket, it is desirable to make the return type of SSLSocket.wrap_bio() and SSLSocket.wrap_socket() customizable. Signed-off-by: Christian Heimes <[email protected]>
1 parent ff70289 commit 4df60f1

File tree

4 files changed

+67
-13
lines changed

4 files changed

+67
-13
lines changed

Doc/library/ssl.rst

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1593,8 +1593,9 @@ to speed up repeated connections from the same clients.
15931593
do_handshake_on_connect=True, suppress_ragged_eofs=True, \
15941594
server_hostname=None, session=None)
15951595

1596-
Wrap an existing Python socket *sock* and return an :class:`SSLSocket`
1597-
object. *sock* must be a :data:`~socket.SOCK_STREAM` socket; other socket
1596+
Wrap an existing Python socket *sock* and return an instance of
1597+
:attr:`SSLContext.sslsocket_class` (default :class:`SSLSocket`).
1598+
*sock* must be a :data:`~socket.SOCK_STREAM` socket; other socket
15981599
types are unsupported.
15991600

16001601
The returned SSL socket is tied to the context, its settings and
@@ -1617,19 +1618,44 @@ to speed up repeated connections from the same clients.
16171618
.. versionchanged:: 3.6
16181619
*session* argument was added.
16191620

1621+
.. versionchanged:: 3.7
1622+
The method returns on instance of :attr:`SSLContext.sslsocket_class`
1623+
instead of hard-coded :class:`SSLSocket`.
1624+
1625+
.. attribute:: SSLContext.sslsocket_class
1626+
1627+
The return type of :meth:`SSLContext.wrap_sockets`, defaults to
1628+
:class:`SSLSocket`. The attribute can be overridden on instance of class
1629+
in order to return a custom subclass of :class:`SSLSocket`.
1630+
1631+
.. versionadded:: 3.7
1632+
16201633
.. method:: SSLContext.wrap_bio(incoming, outgoing, server_side=False, \
16211634
server_hostname=None, session=None)
16221635

1623-
Create a new :class:`SSLObject` instance by wrapping the BIO objects
1624-
*incoming* and *outgoing*. The SSL routines will read input data from the
1625-
incoming BIO and write data to the outgoing BIO.
1636+
Wrap the BIO objects *incoming* and *outgoing* and return an instance of
1637+
attr:`SSLContext.sslobject_class` (default :class:`SSLObject`). The SSL
1638+
routines will read input data from the incoming BIO and write data to the
1639+
outgoing BIO.
16261640

16271641
The *server_side*, *server_hostname* and *session* parameters have the
16281642
same meaning as in :meth:`SSLContext.wrap_socket`.
16291643

16301644
.. versionchanged:: 3.6
16311645
*session* argument was added.
16321646

1647+
.. versionchanged:: 3.7
1648+
The method returns on instance of :attr:`SSLContext.sslobject_class`
1649+
instead of hard-coded :class:`SSLObject`.
1650+
1651+
.. attribute:: SSLContext.sslobject_class
1652+
1653+
The return type of :meth:`SSLContext.wrap_bio`, defaults to
1654+
:class:`SSLObject`. The attribute can be overridden on instance of class
1655+
in order to return a custom subclass of :class:`SSLObject`.
1656+
1657+
.. versionadded:: 3.7
1658+
16331659
.. method:: SSLContext.session_stats()
16341660

16351661
Get statistics about the SSL sessions created or managed by this context.

Lib/ssl.py

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -383,10 +383,11 @@ class Purpose(_ASN1Object, _Enum):
383383
class SSLContext(_SSLContext):
384384
"""An SSLContext holds various SSL-related configuration options and
385385
data, such as certificates and possibly a private key."""
386-
387-
__slots__ = ('protocol', '__weakref__')
388386
_windows_cert_stores = ("CA", "ROOT")
389387

388+
sslsocket_class = None # SSLSocket is assigned later.
389+
sslobject_class = None # SSLObject is assigned later.
390+
390391
def __new__(cls, protocol=PROTOCOL_TLS, *args, **kwargs):
391392
self = _SSLContext.__new__(cls, protocol)
392393
if protocol != _SSLv2_IF_EXISTS:
@@ -400,17 +401,21 @@ def wrap_socket(self, sock, server_side=False,
400401
do_handshake_on_connect=True,
401402
suppress_ragged_eofs=True,
402403
server_hostname=None, session=None):
403-
return SSLSocket(sock=sock, server_side=server_side,
404-
do_handshake_on_connect=do_handshake_on_connect,
405-
suppress_ragged_eofs=suppress_ragged_eofs,
406-
server_hostname=server_hostname,
407-
_context=self, _session=session)
404+
return self.sslsocket_class(
405+
sock=sock,
406+
server_side=server_side,
407+
do_handshake_on_connect=do_handshake_on_connect,
408+
suppress_ragged_eofs=suppress_ragged_eofs,
409+
server_hostname=server_hostname,
410+
_context=self,
411+
_session=session
412+
)
408413

409414
def wrap_bio(self, incoming, outgoing, server_side=False,
410415
server_hostname=None, session=None):
411416
sslobj = self._wrap_bio(incoming, outgoing, server_side=server_side,
412417
server_hostname=server_hostname)
413-
return SSLObject(sslobj, session=session)
418+
return self.sslobject_class(sslobj, session=session)
414419

415420
def set_npn_protocols(self, npn_protocols):
416421
protos = bytearray()
@@ -1135,6 +1140,11 @@ def version(self):
11351140
return self._sslobj.version()
11361141

11371142

1143+
# Python does not support forward declaration of types.
1144+
SSLContext.sslsocket_class = SSLSocket
1145+
SSLContext.sslobject_class = SSLObject
1146+
1147+
11381148
def wrap_socket(sock, keyfile=None, certfile=None,
11391149
server_side=False, cert_reqs=CERT_NONE,
11401150
ssl_version=PROTOCOL_TLS, ca_certs=None,

Lib/test/test_ssl.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1359,6 +1359,22 @@ def test_context_client_server(self):
13591359
self.assertFalse(ctx.check_hostname)
13601360
self.assertEqual(ctx.verify_mode, ssl.CERT_NONE)
13611361

1362+
def test_context_custom_class(self):
1363+
class MySSLSocket(ssl.SSLSocket):
1364+
pass
1365+
1366+
class MySSLObject(ssl.SSLObject):
1367+
pass
1368+
1369+
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
1370+
ctx.sslsocket_class = MySSLSocket
1371+
ctx.sslobject_class = MySSLObject
1372+
1373+
with ctx.wrap_socket(socket.socket(), server_side=True) as sock:
1374+
self.assertIsInstance(sock, MySSLSocket)
1375+
obj = ctx.wrap_bio(ssl.MemoryBIO(), ssl.MemoryBIO())
1376+
self.assertIsInstance(obj, MySSLObject)
1377+
13621378

13631379
class SSLErrorTests(unittest.TestCase):
13641380

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Make return types of SSLContext.wrap_bio() and SSLContext.wrap_socket()
2+
customizable.

0 commit comments

Comments
 (0)