Require modern ssl features (SSLContext, etc)
Now that we've dropped python 3.3 (so create_default_context is present on all supported versions), we can drop all ssl backwards-compatibility and require the modern feature set.
This commit is contained in:
parent
648b3e9f7c
commit
8cf55df456
|
@ -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.
|
||||
#
|
||||
|
|
17
setup.py
17
setup.py
|
@ -16,6 +16,7 @@
|
|||
|
||||
import os
|
||||
import platform
|
||||
import ssl
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
|
@ -129,18 +130,22 @@ if setuptools is not None:
|
|||
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,
|
||||
|
|
|
@ -37,7 +37,7 @@ import re
|
|||
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
|
||||
|
||||
|
@ -1382,8 +1382,8 @@ class SSLIOStream(IOStream):
|
|||
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:
|
||||
|
|
|
@ -35,42 +35,16 @@ except ImportError:
|
|||
# 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,
|
||||
|
@ -487,11 +461,10 @@ 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
|
||||
context = ssl.SSLContext(
|
||||
ssl_options.get('ssl_version', ssl.PROTOCOL_SSLv23))
|
||||
if 'certfile' in ssl_options:
|
||||
|
@ -519,14 +492,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)
|
||||
|
|
|
@ -35,18 +35,6 @@ except ImportError:
|
|||
# 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.
|
||||
|
@ -261,11 +249,6 @@ class _HTTPConnection(httputil.HTTPMessageDelegate):
|
|||
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:
|
||||
|
|
|
@ -150,7 +150,6 @@ class TLSv1Test(BaseSSLTest, SSLTestMixin):
|
|||
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(
|
||||
|
|
|
@ -880,7 +880,6 @@ class TestIOStreamSSL(TestIOStreamMixin, AsyncTestCase):
|
|||
# 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)
|
||||
|
@ -983,8 +982,6 @@ class TestIOStreamStartTLS(AsyncTestCase):
|
|||
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.
|
||||
|
|
|
@ -497,8 +497,6 @@ class SimpleHTTPSClientTestCase(SimpleHTTPClientTestMixin, AsyncHTTPSTestCase):
|
|||
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))
|
||||
|
@ -511,8 +509,6 @@ class SimpleHTTPSClientTestCase(SimpleHTTPClientTestMixin, AsyncHTTPSTestCase):
|
|||
"/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)
|
||||
|
|
Loading…
Reference in New Issue