Skip to content

bpo-31386: Custom wrap_bio and wrap_socket type #3426

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 15, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 31 additions & 5 deletions Doc/library/ssl.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1554,8 +1554,9 @@ to speed up repeated connections from the same clients.
do_handshake_on_connect=True, suppress_ragged_eofs=True, \
server_hostname=None, session=None)

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

The returned SSL socket is tied to the context, its settings and
Expand All @@ -1578,19 +1579,44 @@ to speed up repeated connections from the same clients.
.. versionchanged:: 3.6
*session* argument was added.

.. versionchanged:: 3.7
The method returns on instance of :attr:`SSLContext.sslsocket_class`
instead of hard-coded :class:`SSLSocket`.

.. attribute:: SSLContext.sslsocket_class

The return type of :meth:`SSLContext.wrap_sockets`, defaults to
:class:`SSLSocket`. The attribute can be overridden on instance of class
in order to return a custom subclass of :class:`SSLSocket`.

.. versionadded:: 3.7

.. method:: SSLContext.wrap_bio(incoming, outgoing, server_side=False, \
server_hostname=None, session=None)

Create a new :class:`SSLObject` instance by wrapping the BIO objects
*incoming* and *outgoing*. The SSL routines will read input data from the
incoming BIO and write data to the outgoing BIO.
Wrap the BIO objects *incoming* and *outgoing* and return an instance of
attr:`SSLContext.sslobject_class` (default :class:`SSLObject`). The SSL
routines will read input data from the incoming BIO and write data to the
outgoing BIO.

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

.. versionchanged:: 3.6
*session* argument was added.

.. versionchanged:: 3.7
The method returns on instance of :attr:`SSLContext.sslobject_class`
instead of hard-coded :class:`SSLObject`.

.. attribute:: SSLContext.sslobject_class

The return type of :meth:`SSLContext.wrap_bio`, defaults to
:class:`SSLObject`. The attribute can be overridden on instance of class
in order to return a custom subclass of :class:`SSLObject`.

.. versionadded:: 3.7

.. method:: SSLContext.session_stats()

Get statistics about the SSL sessions created or managed by this context.
Expand Down
26 changes: 18 additions & 8 deletions Lib/ssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -377,10 +377,11 @@ class Purpose(_ASN1Object, _Enum):
class SSLContext(_SSLContext):
"""An SSLContext holds various SSL-related configuration options and
data, such as certificates and possibly a private key."""

__slots__ = ('protocol', '__weakref__')
_windows_cert_stores = ("CA", "ROOT")

sslsocket_class = None # SSLSocket is assigned later.
sslobject_class = None # SSLObject is assigned later.

def __new__(cls, protocol=PROTOCOL_TLS, *args, **kwargs):
self = _SSLContext.__new__(cls, protocol)
if protocol != _SSLv2_IF_EXISTS:
Expand All @@ -394,17 +395,21 @@ def wrap_socket(self, sock, server_side=False,
do_handshake_on_connect=True,
suppress_ragged_eofs=True,
server_hostname=None, session=None):
return SSLSocket(sock=sock, server_side=server_side,
do_handshake_on_connect=do_handshake_on_connect,
suppress_ragged_eofs=suppress_ragged_eofs,
server_hostname=server_hostname,
_context=self, _session=session)
return self.sslsocket_class(
sock=sock,
server_side=server_side,
do_handshake_on_connect=do_handshake_on_connect,
suppress_ragged_eofs=suppress_ragged_eofs,
server_hostname=server_hostname,
_context=self,
_session=session
)

def wrap_bio(self, incoming, outgoing, server_side=False,
server_hostname=None, session=None):
sslobj = self._wrap_bio(incoming, outgoing, server_side=server_side,
server_hostname=server_hostname)
return SSLObject(sslobj, session=session)
return self.sslobject_class(sslobj, session=session)

def set_npn_protocols(self, npn_protocols):
protos = bytearray()
Expand Down Expand Up @@ -1128,6 +1133,11 @@ def version(self):
return self._sslobj.version()


# Python does not support forward declaration of types.
SSLContext.sslsocket_class = SSLSocket
SSLContext.sslobject_class = SSLObject


def wrap_socket(sock, keyfile=None, certfile=None,
server_side=False, cert_reqs=CERT_NONE,
ssl_version=PROTOCOL_TLS, ca_certs=None,
Expand Down
16 changes: 16 additions & 0 deletions Lib/test/test_ssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -1348,6 +1348,22 @@ def test_context_client_server(self):
self.assertFalse(ctx.check_hostname)
self.assertEqual(ctx.verify_mode, ssl.CERT_NONE)

def test_context_custom_class(self):
class MySSLSocket(ssl.SSLSocket):
pass

class MySSLObject(ssl.SSLObject):
pass

ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ctx.sslsocket_class = MySSLSocket
ctx.sslobject_class = MySSLObject

with ctx.wrap_socket(socket.socket(), server_side=True) as sock:
self.assertIsInstance(sock, MySSLSocket)
obj = ctx.wrap_bio(ssl.MemoryBIO(), ssl.MemoryBIO())
self.assertIsInstance(obj, MySSLObject)


class SSLErrorTests(unittest.TestCase):

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Make return types of SSLContext.wrap_bio() and SSLContext.wrap_socket()
customizable.