]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
Make SSLIOStream compatible with SSLv3- and TLSv1-only servers.
authorBen Darnell <ben@bendarnell.com>
Mon, 9 Jan 2012 18:33:01 +0000 (10:33 -0800)
committerBen Darnell <ben@bendarnell.com>
Mon, 9 Jan 2012 19:56:44 +0000 (11:56 -0800)
Due to some implementation detail the default SSLv23 mode allows reads
before the handshake has completed, but the other modes do not.

Closes #431.

tornado/httpclient.py
tornado/iostream.py
tornado/test/httpserver_test.py

index 1280a47d64e1f693c505d0b0f9ea39d3dbecdb80..354d907c374082388e93153ed236799c3a2c507c 100644 (file)
@@ -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:
index 460f7f349f6b04f9dfc39e33def9fd0ce5abbf85..db7895f0ed057e4639b42622801838b358393ce2 100644 (file)
@@ -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().
index 5eb46208306cd1e8947ee1619eeeba68941a8ee2..1f0fb23c2b6c7fa7150f4930fda0cfc3e2c17b02 100644 (file)
@@ -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):