[issue1251] ssl module doesn't support non-blocking handshakes
New submission from Chris Stawarz: The current version of the ssl module doesn't support non-blocking creation of SSLSocket objects. The reason for this is that the SSL handshaking (SSL_connect/SSL_accept) takes place during the construction of the SSLContext object (in newPySSLObject). This means that if the socket being wrapped is non-blocking, and the handshake fails with SSL_ERROR_WANT_READ/SSL_ERROR_WANT_WRITE, then the entire SSLContext is scrapped, and newPySSLObject must be run again in its entirety. Unfortunately, restarting from scratch on the same socket appears to confuse the remote host, and the new attempt fails. The attached patch fixes this problem by removing the handshaking code from newPySSLObject and adding a do_handshake method to SSLContext. It also adds a new parameter (do_handshake_on_connect) to the SSLSocket constructor and the wrap_socket function. The default value of the parameter is True, which preserves the current behavior of the module by immediately calling do_handshake after sslwrap. If do_handshake_on_connect is set to False, then the caller is responsible for calling do_handshake. This allows code that uses non-blocking sockets to first create the SSLSocket and then iteratively call do_handshake and select.select until the process completes (which is exactly how non-blocking reads and writes are handled). -- components: Documentation, Library (Lib), Tests files: ssl_nonblocking_handshake_patch.txt messages: 56295 nosy: chris.stawarz severity: normal status: open title: ssl module doesn't support non-blocking handshakes type: rfe versions: Python 2.6 __ Tracker <[EMAIL PROTECTED]> <http://bugs.python.org/issue1251> __Index: Doc/library/ssl.rst === --- Doc/library/ssl.rst (revision 58397) +++ Doc/library/ssl.rst (working copy) @@ -54,7 +54,7 @@ network connection. This error is a subtype of :exc:`socket.error`, which in turn is a subtype of :exc:`IOError`. -.. function:: wrap_socket (sock, keyfile=None, certfile=None, server_side=False, cert_reqs=CERT_NONE, ssl_version={see docs}, ca_certs=None) +.. function:: wrap_socket (sock, keyfile=None, certfile=None, server_side=False, cert_reqs=CERT_NONE, ssl_version={see docs}, ca_certs=None, do_handshake_on_connect=True) Takes an instance ``sock`` of :class:`socket.socket`, and returns an instance of :class:`ssl.SSLSocket`, a subtype of :class:`socket.socket`, which wraps the underlying socket in an SSL context. @@ -98,6 +98,10 @@ See the discussion of :ref:`ssl-certificates` for more information about how to arrange the certificates in this file. + The parameter ``do_handshake_on_connect`` is a boolean that indicates whether a TLS/SSL + handshake should be initiated as soon as the socket is connected. If False, the + socket's :meth:`do_handshake` method must be called to perform a handshake. + The parameter ``ssl_version`` specifies which version of the SSL protocol to use. Typically, the server chooses a particular protocol version, and the client must adapt to the server's choice. Most of the versions are not interoperable @@ -289,7 +293,11 @@ number of secret bits being used. If no connection has been established, returns ``None``. +.. method:: SSLSocket.do_handshake() + Perform a TLS/SSL handshake. + + .. index:: single: certificates .. index:: single: X509 certificate Index: Lib/ssl.py === --- Lib/ssl.py (revision 58397) +++ Lib/ssl.py (working copy) @@ -86,7 +86,8 @@ def __init__(self, sock, keyfile=None, certfile=None, server_side=False, cert_reqs=CERT_NONE, - ssl_version=PROTOCOL_SSLv23, ca_certs=None): + ssl_version=PROTOCOL_SSLv23, ca_certs=None, + do_handshake_on_connect=True): socket.__init__(self, _sock=sock._sock) if certfile and not keyfile: keyfile = certfile @@ -101,11 +102,14 @@ self._sslobj = _ssl.sslwrap(self._sock, server_side, keyfile, certfile, cert_reqs, ssl_version, ca_certs) +if do_handshake_on_connect: +self.do_handshake() self.keyfile = keyfile self.certfile = certfile self.cert_reqs = cert_reqs self.ssl_version = ssl_version self.ca_certs = ca_certs +self.do_handshake_on_connect = do_handshake_on_connect def read(self, len=1024): @@ -189,6 +193,12 @@ self._sslobj = None socket.close(self) +def do_handshake(self): + +"""Perform a TLS/SSL handshake.""" + +self._sslobj.do_handshake() + def connect(self, addr):
[issue1251] ssl module doesn't support non-blocking handshakes
Chris Stawarz added the comment: Yeah, the pattern for doing non-blocking I/O with select() is different for SSL-wrapped sockets: You always have to try the potentially-blocking operation first, and then call select() and retry in response to SSL_ERROR_WANT_READ/WRITE. (You can also check SSL_pending(), but I don't think you really have to.) Also, unlike normal sockets, SSL-wrapped sockets *must* be set non-blocking. I can see how this pattern might not play nicely with asyncore. But I think this is a separate (though related) issue from the one I reported. As it's currently implemented, the ssl module provides no way of wrapping a socket without (potentially) blocking during the handshake, making it unusable by Twisted or any other package that requires all I/O to be non-blocking. Moving the handshaking into a separate method solves this problem. __ Tracker <[EMAIL PROTECTED]> <http://bugs.python.org/issue1251> __ ___ Python-bugs-list mailing list Unsubscribe: http://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com
[issue1251] ssl module doesn't support non-blocking handshakes
Chris Stawarz added the comment: I meant that SSL-wrapped sockets must be set non-blocking in the case where you want to do non-blocking I/O with them using select(). This is another difference between SSL-wrapped sockets and normal sockets. With a normal socket, as long as you use select() to know when a read or write won't block, it shouldn't matter whether you've called setblocking(False) on the socket (although there may be corner cases where it does). With an SSL-wrapped socket, you have to try the I/O operation first, and then call select() if it fails with SSL_ERROR_WANT_READ/WRITE. But that won't happen if the socket is in blocking mode. In that case, the OpenSSL call will just block until the operation completes (or an error or disconnection occurs). That's my understanding, anyway, based on the OpenSSL man pages and my own usage. __ Tracker <[EMAIL PROTECTED]> <http://bugs.python.org/issue1251> __ ___ Python-bugs-list mailing list Unsubscribe: http://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com
[issue1251] ssl module doesn't support non-blocking handshakes
Chris Stawarz added the comment: > The loop in _ssl.c/do_handshake will never return WANT_READ or > WANT_WRITE, so the loop in the test case, for instance, is > unnecessary. I don't know why you think that, but it's easy enough to show that this statement is incorrect. I've attached two scripts (nonblocking_handshake.py and blocking_handshake.py). The first does basically the same thing as my test case, but connecting to a different server and with some print statements added. Here's the output I get when I run it using an up-to-date trunk checkout with my patch applied: $ ../build/bin/python2.6 nonblocking_handshake.py starting handshake need read need read need read handshake complete The second reproduces the situation that led me to file this bug report in the first place. Here's what happens when I run it with an *unpatched* trunk build: $ ../build/bin/python2.6 blocking_handshake.py starting handshake need read Traceback (most recent call last): File "blocking_handshake.py", line 14, in s = ssl.wrap_socket(s,cert_reqs=ssl.CERT_NONE) File "/Users/cstawarz/Documents/Code/Python/svn/build/lib/ python2.6/ssl.py", line 466, in wrap_socket ssl_version=ssl_version, ca_certs=ca_certs) File "/Users/cstawarz/Documents/Code/Python/svn/build/lib/ python2.6/ssl.py", line 103, in __init__ cert_reqs, ssl_version, ca_certs) ssl.SSLError: [Errno 1] _ssl.c:429: error:04077068:rsa routines:RSA_verify:bad signature As you see, in both cases the handshaking fails with SSL_ERROR_WANT_READ. But without the fixes introduced by my patch, there's no way to handle the error. __ Tracker <[EMAIL PROTECTED]> <http://bugs.python.org/issue1251> __import select import socket import ssl s = ssl.wrap_socket(socket.socket(socket.AF_INET), cert_reqs=ssl.CERT_NONE, do_handshake_on_connect=False) s.connect(('people.csail.mit.edu', 443)) s.setblocking(False) print 'starting handshake' while True: try: s.do_handshake() break except ssl.SSLError, err: if err.args[0] == ssl.SSL_ERROR_WANT_READ: print 'need read' select.select([s], [], []) elif err.args[0] == ssl.SSL_ERROR_WANT_WRITE: print 'need write' select.select([], [s], []) else: raise print 'handshake complete' s.close() import select import socket import ssl s = socket.socket(socket.AF_INET) s.connect(('people.csail.mit.edu', 443)) s.setblocking(False) print 'starting handshake' while True: try: s = ssl.wrap_socket(s, cert_reqs=ssl.CERT_NONE) break except ssl.SSLError, err: if err.args[0] == ssl.SSL_ERROR_WANT_READ: print 'need read' select.select([s], [], []) elif err.args[0] == ssl.SSL_ERROR_WANT_WRITE: print 'need write' select.select([], [s], []) else: raise print 'handshake complete' s.close() ___ Python-bugs-list mailing list Unsubscribe: http://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com
[issue1251] ssl module doesn't support non-blocking handshakes
Chris Stawarz added the comment: Bill, You seem to be missing the whole point of doing non-blocking I/O, which is to handle multiple concurrent, I/O-bound tasks in a single OS thread. The application must be able to try the handshake, detect that it didn't complete because I/O needs to take place, and then retry it at a later time of its choosing (presumably when select() says it's OK to read from the socket). And between now and that later time, it can do other things, like read or write to other sockets. The point is that the *application* must have control over when it does I/O, when it waits for sockets to be ready for I/O, and what it does in between. There's just no way it can do this if the sslwrap() function doesn't return until the handshaking is complete. sslwrap() can't "do the right thing", because the right thing is to return control to the application until it's ready to try the handshake again. And this has nothing to do with the GIL or multiple threads. Like I said, the point of doing non-blocking I/O with select() is to *avoid* the need for multiple threads. __ Tracker <[EMAIL PROTECTED]> <http://bugs.python.org/issue1251> __ ___ Python-bugs-list mailing list Unsubscribe: http://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com