# 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.
#
import os
import platform
+import ssl
import sys
import warnings
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,
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
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:
# 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)
else:
# Google App Engine
_client_ssl_defaults = dict(cert_reqs=None,
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
context = ssl.SSLContext(
ssl_options.get('ssl_version', ssl.PROTOCOL_SSLv23))
if 'certfile' in ssl_options:
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)
# 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.
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
if self.request.client_cert is not None:
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(
# 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)
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.
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))
"/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)