[issue1251] ssl module doesn't support non-blocking handshakes

2007-10-09 Thread Chris Stawarz

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

2007-10-10 Thread Chris Stawarz

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

2007-10-10 Thread Chris Stawarz

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

2007-10-12 Thread Chris Stawarz

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

2007-10-15 Thread Chris Stawarz

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