[pypy-svn] pypy default: Really implement _ssl.do_handshake()

amauryfa commits-noreply at bitbucket.org
Tue Jan 18 09:35:37 CET 2011


Author: Amaury Forgeot d'Arc <amauryfa at gmail.com>
Branch: 
Changeset: r40830:b7806f8ff873
Date: 2011-01-18 09:34 +0100
http://bitbucket.org/pypy/pypy/changeset/b7806f8ff873/

Log:	Really implement _ssl.do_handshake() so the sockets can have a
	chance to connect...

diff --git a/pypy/module/_ssl/test/test_ssl.py b/pypy/module/_ssl/test/test_ssl.py
--- a/pypy/module/_ssl/test/test_ssl.py
+++ b/pypy/module/_ssl/test/test_ssl.py
@@ -64,7 +64,9 @@
         import _ssl
         import _socket
         s = _socket.socket()
-        _ssl.sslwrap(s, 0)
+        ss = _ssl.sslwrap(s, 0)
+        exc = raises(_socket.error, ss.do_handshake)
+        assert exc.value.errno == 32 # Broken pipe
 
 class AppTestConnectedSSL:
     def setup_class(cls):

diff --git a/pypy/module/_ssl/app_ssl.py b/pypy/module/_ssl/app_ssl.py
--- a/pypy/module/_ssl/app_ssl.py
+++ b/pypy/module/_ssl/app_ssl.py
@@ -1,4 +1,6 @@
-class SSLError(Exception):
+import _socket
+
+class SSLError(_socket.error):
     pass
 
 __doc__ = """Implementation module for SSL socket operations. 

diff --git a/pypy/module/_ssl/interp_ssl.py b/pypy/module/_ssl/interp_ssl.py
--- a/pypy/module/_ssl/interp_ssl.py
+++ b/pypy/module/_ssl/interp_ssl.py
@@ -4,9 +4,11 @@
 from pypy.interpreter.typedef import TypeDef
 from pypy.interpreter.gateway import interp2app
 
-from pypy.rlib import rpoll
+from pypy.rlib import rpoll, rsocket
 from pypy.rlib.ropenssl import *
 
+from pypy.module._socket import interp_socket
+
 import sys
 
 ## user defined constants
@@ -64,10 +66,15 @@
 constants["OPENSSL_VERSION_INFO"] = (major, minor, fix, patch, status)
 constants["OPENSSL_VERSION"] = SSLEAY_VERSION
 
-def ssl_error(space, msg):
+def ssl_error(space, msg, errno=0):
     w_module = space.getbuiltinmodule('_ssl')
-    w_exception = space.getattr(w_module, space.wrap('SSLError'))
-    return OperationError(w_exception, space.wrap(msg))
+    w_exception_class = space.getattr(w_module, space.wrap('SSLError'))
+    if errno:
+        w_exception = space.call_function(w_exception_class,
+                                          space.wrap(e.errno), space.wrap(msg))
+    else:
+        w_exception = space.call_function(w_exception_class, space.wrap(msg))
+    return OperationError(w_exception_class, w_exception)
 
 if HAVE_OPENSSL_RAND:
     # helper routines for seeding the SSL PRNG
@@ -121,7 +128,7 @@
         self.w_socket = None
         self.ctx = lltype.nullptr(SSL_CTX.TO)
         self.ssl = lltype.nullptr(SSL.TO)
-        self.server_cert = lltype.nullptr(X509.TO)
+        self.peer_cert = lltype.nullptr(X509.TO)
         self._server = lltype.malloc(rffi.CCHARP.TO, X509_NAME_MAXLEN, flavor='raw')
         self._server[0] = '\0'
         self._issuer = lltype.malloc(rffi.CCHARP.TO, X509_NAME_MAXLEN, flavor='raw')
@@ -136,8 +143,8 @@
     issuer.unwrap_spec = ['self']
     
     def __del__(self):
-        if self.server_cert:
-            libssl_X509_free(self.server_cert)
+        if self.peer_cert:
+            libssl_X509_free(self.peer_cert)
         if self.ssl:
             libssl_SSL_free(self.ssl)
         if self.ctx:
@@ -190,8 +197,7 @@
         if num_bytes > 0:
             return self.space.wrap(num_bytes)
         else:
-            errstr, errval = _ssl_seterror(self.space, self, num_bytes)
-            raise ssl_error(self.space, "%s: %d" % (errstr, errval))
+            raise _ssl_seterror(self.space, self, num_bytes)
     write.unwrap_spec = ['self', 'bufferstr']
     
     def read(self, num_bytes=1024):
@@ -235,17 +241,61 @@
                 break
                 
         if count <= 0:
-            errstr, errval = _ssl_seterror(self.space, self, count)
-            raise ssl_error(self.space, "%s: %d" % (errstr, errval))
+            raise _ssl_seterror(self.space, self, count)
 
         result = rffi.str_from_buffer(raw_buf, gc_buf, num_bytes, count)
         rffi.keep_buffer_alive_until_here(raw_buf, gc_buf)
         return self.space.wrap(result)
     read.unwrap_spec = ['self', int]
 
-    def do_handshake(self):
-        # XXX
-        pass
+    def do_handshake(self, space):
+        # just in case the blocking state of the socket has been changed
+        w_timeout = space.call_method(self.w_socket, "gettimeout")
+        nonblocking = not space.is_w(w_timeout, space.w_None)
+        libssl_BIO_set_nbio(libssl_SSL_get_rbio(self.ssl), nonblocking)
+        libssl_BIO_set_nbio(libssl_SSL_get_wbio(self.ssl), nonblocking)
+
+        # Actually negotiate SSL connection
+        # XXX If SSL_do_handshake() returns 0, it's also a failure.
+        while True:
+            ret = libssl_SSL_do_handshake(self.ssl)
+            err = libssl_SSL_get_error(self.ssl, ret)
+            # XXX PyErr_CheckSignals()
+            if err == SSL_ERROR_WANT_READ:
+                sockstate = check_socket_and_wait_for_timeout(
+                    space, w_sock, False)
+            elif err == SSL_ERROR_WANT_WRITE:
+                sockstate = check_socket_and_wait_for_timeout(
+                    space, w_sock, True)
+            else:
+                sockstate = SOCKET_OPERATION_OK
+            if sockstate == SOCKET_HAS_TIMED_OUT:
+                raise ssl_error(space, "The handshake operation timed out")
+            elif sockstate == SOCKET_HAS_BEEN_CLOSED:
+                raise ssl_error(space, "Underlying socket has been closed.")
+            elif sockstate == SOCKET_TOO_LARGE_FOR_SELECT:
+                raise ssl_error(space, "Underlying socket too large for select().")
+            elif sockstate == SOCKET_IS_NONBLOCKING:
+                break
+
+            if err == SSL_ERROR_WANT_READ or err == SSL_ERROR_WANT_WRITE:
+                continue
+            else:
+                break
+
+        if ret <= 0:
+            raise _ssl_seterror(space, self, ret)
+
+        if self.peer_cert:
+            libssl_X509_free(self.peer_cert)
+        self.peer_cert = libssl_SSL_get_peer_certificate(self.ssl)
+        if self.peer_cert:
+            libssl_X509_NAME_oneline(
+                libssl_X509_get_subject_name(self.peer_cert),
+                self._server, X509_NAME_MAXLEN)
+            libssl_X509_NAME_oneline(
+                libssl_X509_get_issuer_name(self.peer_cert),
+                self._issuer, X509_NAME_MAXLEN)
 
 
 SSLObject.typedef = TypeDef("SSLObject",
@@ -256,7 +306,8 @@
     write = interp2app(SSLObject.write,
         unwrap_spec=SSLObject.write.unwrap_spec),
     read = interp2app(SSLObject.read, unwrap_spec=SSLObject.read.unwrap_spec),
-    do_handshake=interp2app(SSLObject.do_handshake, unwrap_spec=['self']),
+    do_handshake=interp2app(SSLObject.do_handshake,
+                            unwrap_spec=['self', ObjSpace]),
 )
 
 
@@ -402,7 +453,8 @@
                 errval = PY_SSL_ERROR_EOF
             elif ret == -1:
                 # the underlying BIO reported an I/0 error
-                return errstr, errval # sock.errorhandler()?
+                error = rsocket.last_error()
+                return interp_socket.converted_error(space, error)
             else:
                 errstr = "Some I/O error occurred"
                 errval = PY_SSL_ERROR_SYSCALL
@@ -420,7 +472,7 @@
         errstr = "Invalid error code"
         errval = PY_SSL_ERROR_INVALID_ERROR_CODE
 
-    return errstr, errval
+    return ssl_error(space, errstr, errval)
 
 
 def sslwrap(space, w_socket, side, w_key_file=None, w_cert_file=None,

diff --git a/pypy/rlib/ropenssl.py b/pypy/rlib/ropenssl.py
--- a/pypy/rlib/ropenssl.py
+++ b/pypy/rlib/ropenssl.py
@@ -109,6 +109,7 @@
 ssl_external('SSL_set_connect_state', [SSL], lltype.Void)
 ssl_external('SSL_set_accept_state', [SSL], lltype.Void)
 ssl_external('SSL_connect', [SSL], rffi.INT)
+ssl_external('SSL_do_handshake', [SSL], rffi.INT)
 ssl_external('SSL_get_error', [SSL, rffi.INT], rffi.INT)
 
 ssl_external('ERR_get_error', [], rffi.INT)
@@ -150,6 +151,8 @@
 
 def libssl_SSL_CTX_set_options(ctx, op):
     return libssl_SSL_CTX_ctrl(ctx, SSL_CTRL_OPTIONS, op, None)
+def libssl_BIO_set_nbio(bio, nonblocking):
+    return libssl_BIO_ctrl(bio, BIO_C_SET_NBIO, nonblocking, None)
 
 def init_ssl():
     libssl_SSL_load_error_strings()


More information about the Pypy-commit mailing list