Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,10 @@ installed in this way, so you may wish to download a copy of the
source tarball or clone the `git repository
<https://github.com/tornadoweb/tornado>`_ as well.

**Prerequisites**: Tornado runs on Python 2.7, and 3.4+
For Python 2, version 2.7.9 or newer is *strongly*
recommended for the improved SSL support. In addition to the requirements
**Prerequisites**: Tornado runs on Python 2.7, and 3.4+.
The updates to the `ssl` module in Python 2.7.9 are required
(in some distributions, these updates may be available in
older python versions). In addition to the requirements
which will be installed automatically by ``pip`` or ``setup.py install``,
the following optional packages may be useful:

Expand Down
2 changes: 0 additions & 2 deletions maint/requirements.in
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
# Requirements for tools used in the development of tornado.
# This list is for python 3.5; for 2.7 add:
# - backports.ssl-match-hostname
# - futures
# - mock
# - certifi
#
# Use virtualenv instead of venv; tox seems to get confused otherwise.
#
Expand Down
20 changes: 11 additions & 9 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import os
import platform
import ssl
import sys
import warnings

Expand Down Expand Up @@ -126,21 +127,22 @@ def build_extension(self, ext):
if setuptools is not None:
# If setuptools is not available, you're on your own for dependencies.
install_requires = []
if sys.version_info < (2, 7):
# Only needed indirectly, for singledispatch.
install_requires.append('ordereddict')
if sys.version_info < (2, 7, 9):
install_requires.append('backports.ssl_match_hostname')
if sys.version_info < (3, 4):
install_requires.append('singledispatch')
# Certifi is also optional on 2.7.9+, although making our dependencies
# conditional on micro version numbers seems like a bad idea
# until we have more declarative metadata.
install_requires.append('certifi')
if sys.version_info < (3, 5):
install_requires.append('backports_abc>=0.4')
kwargs['install_requires'] = install_requires

# Verify that the SSL module has all the modern upgrades. Check for several
# names individually since they were introduced at different versions,
# although they should all be present by Python 3.4 or 2.7.9.
if (not hasattr(ssl, 'SSLContext') or
not hasattr(ssl, 'create_default_context') or
not hasattr(ssl, 'match_hostname')):
raise ImportError("Tornado requires an up-to-date SSL module. This means "
"Python 2.7.9+ or 3.4+ (although some distributions have "
"backported the necessary changes to older versions).")

setup(
name="tornado",
version=version,
Expand Down
6 changes: 3 additions & 3 deletions tornado/iostream.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
from tornado.concurrent import TracebackFuture
from tornado import ioloop
from tornado.log import gen_log, app_log
from tornado.netutil import ssl_wrap_socket, ssl_match_hostname, SSLCertificateError, _client_ssl_defaults, _server_ssl_defaults
from tornado.netutil import ssl_wrap_socket, _client_ssl_defaults, _server_ssl_defaults
from tornado import stack_context
from tornado.util import errno_from_exception

Expand Down Expand Up @@ -1382,8 +1382,8 @@ def _verify_cert(self, peercert):
gen_log.warning("No SSL certificate given")
return False
try:
ssl_match_hostname(peercert, self._server_hostname)
except SSLCertificateError as e:
ssl.match_hostname(peercert, self._server_hostname)
except ssl.CertificateError as e:
gen_log.warning("Invalid SSL certificate: %s" % e)
return False
else:
Expand Down
76 changes: 28 additions & 48 deletions tornado/netutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,42 +35,20 @@
# ssl is not available on Google App Engine
ssl = None

try:
import certifi
except ImportError:
# certifi is optional as long as we have ssl.create_default_context.
if ssl is None or hasattr(ssl, 'create_default_context'):
certifi = None
else:
raise

if PY3:
xrange = range

if hasattr(ssl, 'match_hostname') and hasattr(ssl, 'CertificateError'): # python 3.2+
ssl_match_hostname = ssl.match_hostname
SSLCertificateError = ssl.CertificateError
elif ssl is None:
ssl_match_hostname = SSLCertificateError = None # type: ignore
else:
import backports.ssl_match_hostname
ssl_match_hostname = backports.ssl_match_hostname.match_hostname
SSLCertificateError = backports.ssl_match_hostname.CertificateError # type: ignore

if hasattr(ssl, 'SSLContext'):
if hasattr(ssl, 'create_default_context'):
# Python 2.7.9+, 3.4+
# Note that the naming of ssl.Purpose is confusing; the purpose
# of a context is to authentiate the opposite side of the connection.
_client_ssl_defaults = ssl.create_default_context(
ssl.Purpose.SERVER_AUTH)
_server_ssl_defaults = ssl.create_default_context(
ssl.Purpose.CLIENT_AUTH)
elif ssl:
# Python 2.6-2.7.8
_client_ssl_defaults = dict(cert_reqs=ssl.CERT_REQUIRED,
ca_certs=certifi.where())
_server_ssl_defaults = {}
if ssl is not None:
# Note that the naming of ssl.Purpose is confusing; the purpose
# of a context is to authentiate the opposite side of the connection.
_client_ssl_defaults = ssl.create_default_context(
ssl.Purpose.SERVER_AUTH)
_server_ssl_defaults = ssl.create_default_context(
ssl.Purpose.CLIENT_AUTH)
if hasattr(ssl, 'OP_NO_COMPRESSION'):
# See netutil.ssl_options_to_context
_client_ssl_defaults.options |= ssl.OP_NO_COMPRESSION
_server_ssl_defaults.options |= ssl.OP_NO_COMPRESSION
else:
# Google App Engine
_client_ssl_defaults = dict(cert_reqs=None,
Expand Down Expand Up @@ -487,11 +465,12 @@ def ssl_options_to_context(ssl_options):
accepts both forms needs to upgrade to the `~ssl.SSLContext` version
to use features like SNI or NPN.
"""
if isinstance(ssl_options, dict):
assert all(k in _SSL_CONTEXT_KEYWORDS for k in ssl_options), ssl_options
if (not hasattr(ssl, 'SSLContext') or
isinstance(ssl_options, ssl.SSLContext)):
if isinstance(ssl_options, ssl.SSLContext):
return ssl_options
assert isinstance(ssl_options, dict)
assert all(k in _SSL_CONTEXT_KEYWORDS for k in ssl_options), ssl_options
# Can't use create_default_context since this interface doesn't
# tell us client vs server.
context = ssl.SSLContext(
ssl_options.get('ssl_version', ssl.PROTOCOL_SSLv23))
if 'certfile' in ssl_options:
Expand All @@ -504,7 +483,9 @@ def ssl_options_to_context(ssl_options):
context.set_ciphers(ssl_options['ciphers'])
if hasattr(ssl, 'OP_NO_COMPRESSION'):
# Disable TLS compression to avoid CRIME and related attacks.
# This constant wasn't added until python 3.3.
# This constant depends on openssl version 1.0.
# TODO: Do we need to do this ourselves or can we trust
# the defaults?
context.options |= ssl.OP_NO_COMPRESSION
return context

Expand All @@ -519,14 +500,13 @@ def ssl_wrap_socket(socket, ssl_options, server_hostname=None, **kwargs):
appropriate).
"""
context = ssl_options_to_context(ssl_options)
if hasattr(ssl, 'SSLContext') and isinstance(context, ssl.SSLContext):
if server_hostname is not None and getattr(ssl, 'HAS_SNI'):
# Python doesn't have server-side SNI support so we can't
# really unittest this, but it can be manually tested with
# python3.2 -m tornado.httpclient https://sni.velox.ch
return context.wrap_socket(socket, server_hostname=server_hostname,
**kwargs)
else:
return context.wrap_socket(socket, **kwargs)
if ssl.HAS_SNI:
# In python 3.4, wrap_socket only accepts the server_hostname
# argument if HAS_SNI is true.
# TODO: add a unittest (python added server-side SNI support in 3.4)
# In the meantime it can be manually tested with
# python3 -m tornado.httpclient https://sni.velox.ch
return context.wrap_socket(socket, server_hostname=server_hostname,
**kwargs)
else:
return ssl.wrap_socket(socket, **dict(context, **kwargs)) # type: ignore
return context.wrap_socket(socket, **kwargs)
59 changes: 12 additions & 47 deletions tornado/simple_httpclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,6 @@
# ssl is not available on Google App Engine.
ssl = None

try:
import certifi
except ImportError:
certifi = None


def _default_ca_certs():
if certifi is None:
raise Exception("The 'certifi' package is required to use https "
"in simple_httpclient")
return certifi.where()


class SimpleAsyncHTTPClient(AsyncHTTPClient):
"""Non-blocking HTTP client with no external dependencies.
Expand Down Expand Up @@ -256,42 +244,19 @@ def _get_ssl_options(self, scheme):
self.request.client_cert is None and
self.request.client_key is None):
return _client_ssl_defaults
ssl_options = {}
if self.request.validate_cert:
ssl_options["cert_reqs"] = ssl.CERT_REQUIRED
if self.request.ca_certs is not None:
ssl_options["ca_certs"] = self.request.ca_certs
elif not hasattr(ssl, 'create_default_context'):
# When create_default_context is present,
# we can omit the "ca_certs" parameter entirely,
# which avoids the dependency on "certifi" for py34.
ssl_options["ca_certs"] = _default_ca_certs()
if self.request.client_key is not None:
ssl_options["keyfile"] = self.request.client_key
ssl_ctx = ssl.create_default_context(
ssl.Purpose.SERVER_AUTH,
cafile=self.request.ca_certs)
if not self.request.validate_cert:
ssl_ctx.check_hostname = False
ssl_ctx.verify_mode = ssl.CERT_NONE
if self.request.client_cert is not None:
ssl_options["certfile"] = self.request.client_cert

# SSL interoperability is tricky. We want to disable
# SSLv2 for security reasons; it wasn't disabled by default
# until openssl 1.0. The best way to do this is to use
# the SSL_OP_NO_SSLv2, but that wasn't exposed to python
# until 3.2. Python 2.7 adds the ciphers argument, which
# can also be used to disable SSLv2. As a last resort
# on python 2.6, we set ssl_version to TLSv1. This is
# more narrow than we'd like since it also breaks
# compatibility with servers configured for SSLv3 only,
# but nearly all servers support both SSLv3 and TLSv1:
# http://blog.ivanristic.com/2011/09/ssl-survey-protocol-support.html
if sys.version_info >= (2, 7):
# In addition to disabling SSLv2, we also exclude certain
# classes of insecure ciphers.
ssl_options["ciphers"] = "DEFAULT:!SSLv2:!EXPORT:!DES"
else:
# This is really only necessary for pre-1.0 versions
# of openssl, but python 2.6 doesn't expose version
# information.
ssl_options["ssl_version"] = ssl.PROTOCOL_TLSv1
return ssl_options
ssl_ctx.load_cert_chain(self.request.client_cert,
self.request.client_key)
if hasattr(ssl, 'OP_NO_COMPRESSION'):
# See netutil.ssl_options_to_context
ssl_ctx.options |= ssl.OP_NO_COMPRESSION
return ssl_ctx
return None

def _on_timeout(self, info=None):
Expand Down
1 change: 0 additions & 1 deletion tornado/test/httpserver_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,6 @@ def get_ssl_version(self):
return ssl.PROTOCOL_TLSv1


@unittest.skipIf(not hasattr(ssl, 'SSLContext'), 'ssl.SSLContext not present')
class SSLContextTest(BaseSSLTest, SSLTestMixin):
def get_ssl_options(self):
context = ssl_options_to_context(
Expand Down
3 changes: 0 additions & 3 deletions tornado/test/iostream_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -880,7 +880,6 @@ def _make_client_iostream(self, connection, **kwargs):
# This will run some tests that are basically redundant but it's the
# simplest way to make sure that it works to pass an SSLContext
# instead of an ssl_options dict to the SSLIOStream constructor.
@unittest.skipIf(not hasattr(ssl, 'SSLContext'), 'ssl.SSLContext not present')
class TestIOStreamSSLContext(TestIOStreamMixin, AsyncTestCase):
def _make_server_iostream(self, connection, **kwargs):
context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
Expand Down Expand Up @@ -983,8 +982,6 @@ def test_handshake_fail(self):
with self.assertRaises((ssl.SSLError, socket.error)):
yield server_future

@unittest.skipIf(not hasattr(ssl, 'create_default_context'),
'ssl.create_default_context not present')
@gen_test
def test_check_hostname(self):
# Test that server_hostname parameter to start_tls is being used.
Expand Down
4 changes: 0 additions & 4 deletions tornado/test/runtests.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,6 @@ def main():
# 2.7 and 3.2
warnings.filterwarnings("ignore", category=DeprecationWarning,
message="Please use assert.* instead")
# unittest2 0.6 on py26 reports these as PendingDeprecationWarnings
# instead of DeprecationWarnings.
warnings.filterwarnings("ignore", category=PendingDeprecationWarning,
message="Please use assert.* instead")
# Twisted 15.0.0 triggers some warnings on py3 with -bb.
warnings.filterwarnings("ignore", category=BytesWarning,
module=r"twisted\..*")
Expand Down
4 changes: 0 additions & 4 deletions tornado/test/simple_httpclient_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -497,8 +497,6 @@ def test_ssl_options(self):
resp = self.fetch("/hello", ssl_options={})
self.assertEqual(resp.body, b"Hello world!")

@unittest.skipIf(not hasattr(ssl, 'SSLContext'),
'ssl.SSLContext not present')
def test_ssl_context(self):
resp = self.fetch("/hello",
ssl_options=ssl.SSLContext(ssl.PROTOCOL_SSLv23))
Expand All @@ -511,8 +509,6 @@ def test_ssl_options_handshake_fail(self):
"/hello", ssl_options=dict(cert_reqs=ssl.CERT_REQUIRED))
self.assertRaises(ssl.SSLError, resp.rethrow)

@unittest.skipIf(not hasattr(ssl, 'SSLContext'),
'ssl.SSLContext not present')
def test_ssl_context_handshake_fail(self):
with ExpectLog(gen_log, "SSL Error|Uncaught exception"):
ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
Expand Down
5 changes: 0 additions & 5 deletions tornado/test/twisted_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,6 @@
skipIfNoTwisted = unittest.skipUnless(have_twisted,
"twisted module not present")

skipIfPy26 = unittest.skipIf(sys.version_info < (2, 7),
"twisted incompatible with singledispatch in py26")


def save_signal_handlers():
saved = {}
Expand Down Expand Up @@ -494,7 +491,6 @@ def testTornadoServerTwistedClientReactor(self):
'http://127.0.0.1:%d' % self.tornado_port, self.run_reactor)
self.assertEqual(response, 'Hello from tornado!')

@skipIfPy26
def testTornadoServerTwistedCoroutineClientIOLoop(self):
self.start_tornado_server()
response = self.twisted_coroutine_fetch(
Expand All @@ -503,7 +499,6 @@ def testTornadoServerTwistedCoroutineClientIOLoop(self):


@skipIfNoTwisted
@skipIfPy26
class ConvertDeferredTest(unittest.TestCase):
def test_success(self):
@inlineCallbacks
Expand Down
11 changes: 2 additions & 9 deletions tornado/test/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,8 @@

from tornado.testing import bind_unused_port

# Encapsulate the choice of unittest or unittest2 here.
# To be used as 'from tornado.test.util import unittest'.
if sys.version_info < (2, 7):
# In py26, we must always use unittest2.
import unittest2 as unittest # type: ignore
else:
# Otherwise, use whichever version of unittest was imported in
# tornado.testing.
from tornado.testing import unittest
# Delegate the choice of unittest or unittest2 to tornado.testing.
from tornado.testing import unittest

skipIfNonUnix = unittest.skipIf(os.name != 'posix' or sys.platform == 'cygwin',
"non-unix platform")
Expand Down