From: Ben Darnell Date: Mon, 9 Jan 2012 18:33:01 +0000 (-0800) Subject: Make SSLIOStream compatible with SSLv3- and TLSv1-only servers. X-Git-Tag: v2.2.0~43 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=17052f33c9e467dfc7d3b755146d09012c881046;p=thirdparty%2Ftornado.git Make SSLIOStream compatible with SSLv3- and TLSv1-only servers. Due to some implementation detail the default SSLv23 mode allows reads before the handshake has completed, but the other modes do not. Closes #431. --- diff --git a/tornado/httpclient.py b/tornado/httpclient.py index 1280a47d6..354d907c3 100644 --- a/tornado/httpclient.py +++ b/tornado/httpclient.py @@ -393,12 +393,14 @@ def main(): define("print_headers", type=bool, default=False) define("print_body", type=bool, default=True) define("follow_redirects", type=bool, default=True) + define("validate_cert", type=bool, default=True) args = parse_command_line() client = HTTPClient() for arg in args: try: response = client.fetch(arg, - follow_redirects=options.follow_redirects + follow_redirects=options.follow_redirects, + validate_cert=options.validate_cert, ) except HTTPError, e: if e.response is not None: diff --git a/tornado/iostream.py b/tornado/iostream.py index 460f7f349..db7895f0e 100644 --- a/tornado/iostream.py +++ b/tornado/iostream.py @@ -657,6 +657,11 @@ class SSLIOStream(IOStream): def _read_from_socket(self): + if self._ssl_accepting: + # If the handshake hasn't finished yet, there can't be anything + # to read (attempting to read may or may not raise an exception + # depending on the SSL version) + return None try: # SSLSocket objects have both a read() and recv() method, # while regular sockets only have recv(). diff --git a/tornado/test/httpserver_test.py b/tornado/test/httpserver_test.py index 5eb462083..1f0fb23c2 100644 --- a/tornado/test/httpserver_test.py +++ b/tornado/test/httpserver_test.py @@ -40,9 +40,12 @@ class HelloWorldRequestHandler(RequestHandler): def post(self): self.finish("Got %d bytes in POST" % len(self.request.body)) -class SSLTest(AsyncHTTPTestCase, LogTrapTestCase): +class BaseSSLTest(AsyncHTTPTestCase, LogTrapTestCase): + def get_ssl_version(self): + raise NotImplementedError() + def setUp(self): - super(SSLTest, self).setUp() + super(BaseSSLTest, self).setUp() # Replace the client defined in the parent class. # Some versions of libcurl have deadlock bugs with ssl, # so always run these tests with SimpleAsyncHTTPClient. @@ -59,7 +62,8 @@ class SSLTest(AsyncHTTPTestCase, LogTrapTestCase): test_dir = os.path.dirname(__file__) return dict(ssl_options=dict( certfile=os.path.join(test_dir, 'test.crt'), - keyfile=os.path.join(test_dir, 'test.key'))) + keyfile=os.path.join(test_dir, 'test.key'), + ssl_version=self.get_ssl_version())) def fetch(self, path, **kwargs): self.http_client.fetch(self.get_url(path).replace('http', 'https'), @@ -68,6 +72,7 @@ class SSLTest(AsyncHTTPTestCase, LogTrapTestCase): **kwargs) return self.wait() +class SSLTestMixin(object): def test_ssl(self): response = self.fetch('/') self.assertEqual(response.body, b("Hello world")) @@ -88,8 +93,30 @@ class SSLTest(AsyncHTTPTestCase, LogTrapTestCase): response = self.wait() self.assertEqual(response.code, 599) +# Python's SSL implementation differs significantly between versions. +# For example, SSLv3 and TLSv1 throw an exception if you try to read +# from the socket before the handshake is complete, but the default +# of SSLv23 allows it. +class SSLv23Test(BaseSSLTest, SSLTestMixin): + def get_ssl_version(self): return ssl.PROTOCOL_SSLv23 +class SSLv3Test(BaseSSLTest, SSLTestMixin): + def get_ssl_version(self): return ssl.PROTOCOL_SSLv3 +class TLSv1Test(BaseSSLTest, SSLTestMixin): + def get_ssl_version(self): return ssl.PROTOCOL_TLSv1 + if ssl is None: - del SSLTest + del BaseSSLTest + del SSLv23Test + del SSLv3Test + del TLSv1Test +elif getattr(ssl, 'OPENSSL_VERSION_INFO', (0,0)) < (1,0): + # In pre-1.0 versions of openssl, SSLv23 clients always send SSLv2 + # ClientHello messages, which are rejected by SSLv3 and TLSv1 + # servers. Note that while the OPENSSL_VERSION_INFO was formally + # introduced in python3.2, it was present but undocumented in + # python 2.7 + del SSLv3Test + del TLSv1Test class MultipartTestHandler(RequestHandler): def post(self):