On client connections, the optional parameter *server_hostname* specifies
the hostname of the service which we are connecting to. This allows a
single server to host multiple SSL-based services with distinct certificates,
- quite similarly to HTTP virtual hosts. Specifying *server_hostname*
- will raise a :exc:`ValueError` if the OpenSSL library doesn't have support
- for it (that is, if :data:`HAS_SNI` is :const:`False`). Specifying
- *server_hostname* will also raise a :exc:`ValueError` if *server_side*
- is true.
+ quite similarly to HTTP virtual hosts. Specifying *server_hostname* will
+ raise a :exc:`ValueError` if *server_side* is true.
+
+ .. versionchanged:: 3.5
+ Always allow a server_hostname to be passed, even if OpenSSL does not
+ have SNI.
+.. method:: SSLContext.wrap_bio(incoming, outgoing, server_side=False, \
+ server_hostname=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.
+
+ The *server_side* and *server_hostname* parameters have the same meaning as
+ in :meth:`SSLContext.wrap_socket`.
+
.. method:: SSLContext.session_stats()
Get statistics about the SSL sessions created or managed by this context.
self.assertIs(ss.context, ctx2)
self.assertIs(ss._sslobj.context, ctx2)
- if ssl.HAS_SNI:
- ctx.check_hostname = True
- sslobj = ctx.wrap_bio(incoming, outgoing, False, 'svn.python.org')
- else:
- ctx.check_hostname = False
- sslobj = ctx.wrap_bio(incoming, outgoing, False)
+
+class NetworkedBIOTests(unittest.TestCase):
+
+ def ssl_io_loop(self, sock, incoming, outgoing, func, *args, **kwargs):
+ # A simple IO loop. Call func(*args) depending on the error we get
+ # (WANT_READ or WANT_WRITE) move data between the socket and the BIOs.
+ timeout = kwargs.get('timeout', 10)
+ count = 0
+ while True:
+ errno = None
+ count += 1
+ try:
+ ret = func(*args)
+ except ssl.SSLError as e:
+ # Note that we get a spurious -1/SSL_ERROR_SYSCALL for
+ # non-blocking IO. The SSL_shutdown manpage hints at this.
+ # It *should* be safe to just ignore SYS_ERROR_SYSCALL because
+ # with a Memory BIO there's no syscalls (for IO at least).
+ if e.errno not in (ssl.SSL_ERROR_WANT_READ,
+ ssl.SSL_ERROR_WANT_WRITE,
+ ssl.SSL_ERROR_SYSCALL):
+ raise
+ errno = e.errno
+ # Get any data from the outgoing BIO irrespective of any error, and
+ # send it to the socket.
+ buf = outgoing.read()
+ sock.sendall(buf)
+ # If there's no error, we're done. For WANT_READ, we need to get
+ # data from the socket and put it in the incoming BIO.
+ if errno is None:
+ break
+ elif errno == ssl.SSL_ERROR_WANT_READ:
+ buf = sock.recv(32768)
+ if buf:
+ incoming.write(buf)
+ else:
+ incoming.write_eof()
+ if support.verbose:
+ sys.stdout.write("Needed %d calls to complete %s().\n"
+ % (count, func.__name__))
+ return ret
+
+ def test_handshake(self):
+ with support.transient_internet("svn.python.org"):
+ sock = socket.socket(socket.AF_INET)
+ sock.connect(("svn.python.org", 443))
+ incoming = ssl.MemoryBIO()
+ outgoing = ssl.MemoryBIO()
+ ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+ ctx.verify_mode = ssl.CERT_REQUIRED
+ ctx.load_verify_locations(SVN_PYTHON_ORG_ROOT_CERT)
++ ctx.check_hostname = True
++ sslobj = ctx.wrap_bio(incoming, outgoing, False, 'svn.python.org')
+ self.assertIs(sslobj._sslobj.owner, sslobj)
+ self.assertIsNone(sslobj.cipher())
+ self.assertRaises(ValueError, sslobj.getpeercert)
+ if 'tls-unique' in ssl.CHANNEL_BINDING_TYPES:
+ self.assertIsNone(sslobj.get_channel_binding('tls-unique'))
+ self.ssl_io_loop(sock, incoming, outgoing, sslobj.do_handshake)
+ self.assertTrue(sslobj.cipher())
+ self.assertTrue(sslobj.getpeercert())
+ if 'tls-unique' in ssl.CHANNEL_BINDING_TYPES:
+ self.assertTrue(sslobj.get_channel_binding('tls-unique'))
+ self.ssl_io_loop(sock, incoming, outgoing, sslobj.unwrap)
+ self.assertRaises(ssl.SSLError, sslobj.write, b'foo')
+ sock.close()
+
+ def test_read_write_data(self):
+ with support.transient_internet("svn.python.org"):
+ sock = socket.socket(socket.AF_INET)
+ sock.connect(("svn.python.org", 443))
+ incoming = ssl.MemoryBIO()
+ outgoing = ssl.MemoryBIO()
+ ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+ ctx.verify_mode = ssl.CERT_NONE
+ sslobj = ctx.wrap_bio(incoming, outgoing, False)
+ self.ssl_io_loop(sock, incoming, outgoing, sslobj.do_handshake)
+ req = b'GET / HTTP/1.0\r\n\r\n'
+ self.ssl_io_loop(sock, incoming, outgoing, sslobj.write, req)
+ buf = self.ssl_io_loop(sock, incoming, outgoing, sslobj.read, 1024)
+ self.assertEqual(buf[:5], b'HTTP/')
+ self.ssl_io_loop(sock, incoming, outgoing, sslobj.unwrap)
+ sock.close()
+
+
try:
import threading
except ImportError:
&sock, &server_side,
"idna", &hostname))
return NULL;
- #if !HAVE_SNI
- PyMem_Free(hostname);
- PyErr_SetString(PyExc_ValueError, "server_hostname is not supported "
- "by your OpenSSL library");
- return NULL;
- #endif
}
- res = (PyObject *) newPySSLSocket(self, sock, server_side,
- hostname);
+ res = (PyObject *) newPySSLSocket(self, sock, server_side, hostname,
+ NULL, NULL);
if (hostname != NULL)
PyMem_Free(hostname);
return res;
}
- #if HAVE_SNI
+static PyObject *
+context_wrap_bio(PySSLContext *self, PyObject *args, PyObject *kwds)
+{
+ char *kwlist[] = {"incoming", "outgoing", "server_side",
+ "server_hostname", NULL};
+ int server_side;
+ char *hostname = NULL;
+ PyObject *hostname_obj = Py_None, *res;
+ PySSLMemoryBIO *incoming, *outgoing;
+
+ /* server_hostname is either None (or absent), or to be encoded
+ using the idna encoding. */
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!O!i|O:_wrap_bio", kwlist,
+ &PySSLMemoryBIO_Type, &incoming,
+ &PySSLMemoryBIO_Type, &outgoing,
+ &server_side, &hostname_obj))
+ return NULL;
+ if (hostname_obj != Py_None) {
- #else
- PyErr_SetString(PyExc_ValueError, "server_hostname is not supported "
- "by your OpenSSL library");
- return NULL;
- #endif
+ if (!PyArg_Parse(hostname_obj, "et", "idna", &hostname))
+ return NULL;
+ }
+
+ res = (PyObject *) newPySSLSocket(self, NULL, server_side, hostname,
+ incoming, outgoing);
+
+ PyMem_Free(hostname);
+ return res;
+}
+
static PyObject *
session_stats(PySSLContext *self, PyObject *unused)
{