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):
"""Connects to remote ADDR, and then wraps the connection in
@@ -202,6 +212,8 @@
self._sslobj = _ssl.sslwrap(self._sock, False, self.keyfile,
self.certfile,
self.cert_reqs, self.ssl_version,
self.ca_certs)
+ if self.do_handshake_on_connect:
+ self.do_handshake()
def accept(self):
@@ -212,7 +224,7 @@
newsock, addr = socket.accept(self)
return (SSLSocket(newsock, True, self.keyfile, self.certfile,
self.cert_reqs, self.ssl_version,
- self.ca_certs), addr)
+ self.ca_certs, self.do_handshake_on_connect), addr)
def makefile(self, mode='r', bufsize=-1):
@@ -459,11 +471,13 @@
def wrap_socket(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):
return SSLSocket(sock, keyfile=keyfile, certfile=certfile,
server_side=server_side, cert_reqs=cert_reqs,
- ssl_version=ssl_version, ca_certs=ca_certs)
+ ssl_version=ssl_version, ca_certs=ca_certs,
+ do_handshake_on_connect=do_handshake_on_connect)
# some utility functions
@@ -549,5 +563,7 @@
for compability with Python 2.5 and earlier. Will disappear in
Python 3.0."""
- return _ssl.sslwrap(sock._sock, 0, keyfile, certfile, CERT_NONE,
- PROTOCOL_SSLv23, None)
+ ssl_sock = _ssl.sslwrap(sock._sock, 0, keyfile, certfile, CERT_NONE,
+ PROTOCOL_SSLv23, None)
+ ssl_sock.do_handshake()
+ return ssl_sock
Index: Lib/test/test_ssl.py
===================================================================
--- Lib/test/test_ssl.py (revision 58397)
+++ Lib/test/test_ssl.py (working copy)
@@ -3,6 +3,7 @@
import sys
import unittest
from test import test_support
+import select
import socket
import errno
import subprocess
@@ -57,6 +58,25 @@
s.close()
+ def testNonBlockingHandshake(self):
+ s = ssl.wrap_socket(socket.socket(socket.AF_INET),
+ cert_reqs=ssl.CERT_NONE,
+ do_handshake_on_connect=False)
+ s.connect(("svn.python.org", 443))
+ s.setblocking(False)
+ while True:
+ try:
+ s.do_handshake()
+ break
+ except ssl.SSLError, err:
+ if err.args[0] == ssl.SSL_ERROR_WANT_READ:
+ select.select([s], [], [])
+ elif err.args[0] == ssl.SSL_ERROR_WANT_WRITE:
+ select.select([], [s], [])
+ else:
+ raise
+ s.close()
+
def testCrucialConstants(self):
ssl.PROTOCOL_SSLv2
ssl.PROTOCOL_SSLv23
Index: Modules/_ssl.c
===================================================================
--- Modules/_ssl.c (revision 58397)
+++ Modules/_ssl.c (working copy)
@@ -265,8 +265,6 @@
PySSLObject *self;
char *errstr = NULL;
int ret;
- int err;
- int sockstate;
int verification_mode;
self = PyObject_New(PySSLObject, &PySSL_Type); /* Create new object */
@@ -388,57 +386,6 @@
SSL_set_accept_state(self->ssl);
PySSL_END_ALLOW_THREADS
- /* Actually negotiate SSL connection */
- /* XXX If SSL_connect() returns 0, it's also a failure. */
- sockstate = 0;
- do {
- PySSL_BEGIN_ALLOW_THREADS
- if (socket_type == PY_SSL_CLIENT)
- ret = SSL_connect(self->ssl);
- else
- ret = SSL_accept(self->ssl);
- err = SSL_get_error(self->ssl, ret);
- PySSL_END_ALLOW_THREADS
- if(PyErr_CheckSignals()) {
- goto fail;
- }
- if (err == SSL_ERROR_WANT_READ) {
- sockstate = check_socket_and_wait_for_timeout(Sock, 0);
- } else if (err == SSL_ERROR_WANT_WRITE) {
- sockstate = check_socket_and_wait_for_timeout(Sock, 1);
- } else {
- sockstate = SOCKET_OPERATION_OK;
- }
- if (sockstate == SOCKET_HAS_TIMED_OUT) {
- PyErr_SetString(PySSLErrorObject,
- ERRSTR("The connect operation timed out"));
- goto fail;
- } else if (sockstate == SOCKET_HAS_BEEN_CLOSED) {
- PyErr_SetString(PySSLErrorObject,
- ERRSTR("Underlying socket has been closed."));
- goto fail;
- } else if (sockstate == SOCKET_TOO_LARGE_FOR_SELECT) {
- PyErr_SetString(PySSLErrorObject,
- ERRSTR("Underlying socket too large for select()."));
- goto fail;
- } else if (sockstate == SOCKET_IS_NONBLOCKING) {
- break;
- }
- } while (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE);
- if (ret < 1) {
- PySSL_SetError(self, ret, __FILE__, __LINE__);
- goto fail;
- }
- self->ssl->debug = 1;
-
- PySSL_BEGIN_ALLOW_THREADS
- if ((self->peer_cert = SSL_get_peer_certificate(self->ssl))) {
- X509_NAME_oneline(X509_get_subject_name(self->peer_cert),
- self->server, X509_NAME_MAXLEN);
- X509_NAME_oneline(X509_get_issuer_name(self->peer_cert),
- self->issuer, X509_NAME_MAXLEN);
- }
- PySSL_END_ALLOW_THREADS
self->Socket = Sock;
Py_INCREF(self->Socket);
return self;
@@ -488,6 +435,65 @@
/* SSL object methods */
+static PyObject *PySSL_SSLdo_handshake(PySSLObject *self)
+{
+ int ret;
+ int err;
+ int sockstate;
+
+ /* Actually negotiate SSL connection */
+ /* XXX If SSL_do_handshake() returns 0, it's also a failure. */
+ sockstate = 0;
+ do {
+ PySSL_BEGIN_ALLOW_THREADS
+ ret = SSL_do_handshake(self->ssl);
+ err = SSL_get_error(self->ssl, ret);
+ PySSL_END_ALLOW_THREADS
+ if(PyErr_CheckSignals()) {
+ return NULL;
+ }
+ if (err == SSL_ERROR_WANT_READ) {
+ sockstate =
check_socket_and_wait_for_timeout(self->Socket, 0);
+ } else if (err == SSL_ERROR_WANT_WRITE) {
+ sockstate =
check_socket_and_wait_for_timeout(self->Socket, 1);
+ } else {
+ sockstate = SOCKET_OPERATION_OK;
+ }
+ if (sockstate == SOCKET_HAS_TIMED_OUT) {
+ PyErr_SetString(PySSLErrorObject,
+ ERRSTR("The handshake operation timed out"));
+ return NULL;
+ } else if (sockstate == SOCKET_HAS_BEEN_CLOSED) {
+ PyErr_SetString(PySSLErrorObject,
+ ERRSTR("Underlying socket has been closed."));
+ return NULL;
+ } else if (sockstate == SOCKET_TOO_LARGE_FOR_SELECT) {
+ PyErr_SetString(PySSLErrorObject,
+ ERRSTR("Underlying socket too large for select()."));
+ return NULL;
+ } else if (sockstate == SOCKET_IS_NONBLOCKING) {
+ break;
+ }
+ } while (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE);
+ if (ret < 1)
+ return PySSL_SetError(self, ret, __FILE__, __LINE__);
+ self->ssl->debug = 1;
+
+ if (self->peer_cert)
+ X509_free (self->peer_cert);
+ PySSL_BEGIN_ALLOW_THREADS
+ if ((self->peer_cert = SSL_get_peer_certificate(self->ssl))) {
+ X509_NAME_oneline(X509_get_subject_name(self->peer_cert),
+ self->server, X509_NAME_MAXLEN);
+ X509_NAME_oneline(X509_get_issuer_name(self->peer_cert),
+ self->issuer, X509_NAME_MAXLEN);
+ }
+ PySSL_END_ALLOW_THREADS
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
static PyObject *
PySSL_server(PySSLObject *self)
{
@@ -1286,6 +1292,7 @@
Read up to len bytes from the SSL socket.");
static PyMethodDef PySSLMethods[] = {
+ {"do_handshake", (PyCFunction)PySSL_SSLdo_handshake, METH_NOARGS},
{"write", (PyCFunction)PySSL_SSLwrite, METH_VARARGS,
PySSL_SSLwrite_doc},
{"read", (PyCFunction)PySSL_SSLread, METH_VARARGS,
_______________________________________________
Python-bugs-list mailing list
Unsubscribe:
http://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com