]> granicus.if.org Git - python/commitdiff
Issue #22641: In asyncio, the default SSL context for client connections is now creat...
authorAntoine Pitrou <solipsis@pitrou.net>
Wed, 15 Oct 2014 14:58:21 +0000 (16:58 +0200)
committerAntoine Pitrou <solipsis@pitrou.net>
Wed, 15 Oct 2014 14:58:21 +0000 (16:58 +0200)
Lib/asyncio/selector_events.py
Lib/asyncio/test_utils.py
Lib/test/test_asyncio/test_events.py
Misc/NEWS

index a55eff7876745388e333c073a62eb7b6366b92f3..c5debf8f02bf1e0615ec4f882fd7882e1ebf4e1d 100644 (file)
@@ -689,16 +689,17 @@ class _SelectorSslTransport(_SelectorTransport):
             if not sslcontext:
                 # Client side may pass ssl=True to use a default
                 # context; in that case the sslcontext passed is None.
-                # The default is the same as used by urllib with
-                # cadefault=True.
-                if hasattr(ssl, '_create_stdlib_context'):
-                    sslcontext = ssl._create_stdlib_context(
-                        cert_reqs=ssl.CERT_REQUIRED,
-                        check_hostname=bool(server_hostname))
+                # The default is secure for client connections.
+                if hasattr(ssl, 'create_default_context'):
+                    # Python 3.4+: use up-to-date strong settings.
+                    sslcontext = ssl.create_default_context()
+                    if not server_hostname:
+                        sslcontext.check_hostname = False
                 else:
                     # Fallback for Python 3.3.
                     sslcontext = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
                     sslcontext.options |= ssl.OP_NO_SSLv2
+                    sslcontext.options |= ssl.OP_NO_SSLv3
                     sslcontext.set_default_verify_paths()
                     sslcontext.verify_mode = ssl.CERT_REQUIRED
 
index ac7680de45387d165b03820f5994d6b268d1b4be..3e5eee54397fc9e08e0b73a8945b7040cd362ae0 100644 (file)
@@ -91,6 +91,13 @@ class SilentWSGIRequestHandler(WSGIRequestHandler):
 
 class SilentWSGIServer(WSGIServer):
 
+    request_timeout = 2
+
+    def get_request(self):
+        request, client_addr = super().get_request()
+        request.settimeout(self.request_timeout)
+        return request, client_addr
+
     def handle_error(self, request, client_address):
         pass
 
@@ -138,7 +145,8 @@ def _run_test_server(*, address, use_ssl=False, server_cls, server_ssl_cls):
     httpd = server_class(address, SilentWSGIRequestHandler)
     httpd.set_app(app)
     httpd.address = httpd.server_address
-    server_thread = threading.Thread(target=httpd.serve_forever)
+    server_thread = threading.Thread(
+        target=lambda: httpd.serve_forever(poll_interval=0.05))
     server_thread.start()
     try:
         yield httpd
@@ -160,12 +168,15 @@ if hasattr(socket, 'AF_UNIX'):
 
     class UnixWSGIServer(UnixHTTPServer, WSGIServer):
 
+        request_timeout = 2
+
         def server_bind(self):
             UnixHTTPServer.server_bind(self)
             self.setup_environ()
 
         def get_request(self):
             request, client_addr = super().get_request()
+            request.settimeout(self.request_timeout)
             # Code in the stdlib expects that get_request
             # will return a socket and a tuple (host, port).
             # However, this isn't true for UNIX sockets,
index a305e66d5a33e1719281c891b9ebf68fa1846470..fe1e3add3123f8960d0350e4f542c1395505805a 100644 (file)
@@ -606,15 +606,43 @@ class EventLoopTestsMixin:
         self.assertGreater(pr.nbytes, 0)
         tr.close()
 
+    def _dummy_ssl_create_context(self, purpose=ssl.Purpose.SERVER_AUTH, *,
+                                  cafile=None, capath=None, cadata=None):
+        """
+        A ssl.create_default_context() replacement that doesn't enable
+        cert validation.
+        """
+        self.assertEqual(purpose, ssl.Purpose.SERVER_AUTH)
+        return test_utils.dummy_ssl_context()
+
+    def _test_create_ssl_connection(self, httpd, create_connection,
+                                    check_sockname=True):
+        conn_fut = create_connection(ssl=test_utils.dummy_ssl_context())
+        self._basetest_create_ssl_connection(conn_fut, check_sockname)
+
+        # With ssl=True, ssl.create_default_context() should be called
+        with mock.patch('ssl.create_default_context',
+                        side_effect=self._dummy_ssl_create_context) as m:
+            conn_fut = create_connection(ssl=True)
+            self._basetest_create_ssl_connection(conn_fut, check_sockname)
+            self.assertEqual(m.call_count, 1)
+
+        # With the real ssl.create_default_context(), certificate
+        # validation will fail
+        with self.assertRaises(ssl.SSLError) as cm:
+            conn_fut = create_connection(ssl=True)
+            self._basetest_create_ssl_connection(conn_fut, check_sockname)
+
+        self.assertEqual(cm.exception.reason, 'CERTIFICATE_VERIFY_FAILED')
+
     @unittest.skipIf(ssl is None, 'No ssl module')
     def test_create_ssl_connection(self):
         with test_utils.run_test_server(use_ssl=True) as httpd:
-            conn_fut = self.loop.create_connection(
+            create_connection = functools.partial(
+                self.loop.create_connection,
                 lambda: MyProto(loop=self.loop),
-                *httpd.address,
-                ssl=test_utils.dummy_ssl_context())
-
-            self._basetest_create_ssl_connection(conn_fut)
+                *httpd.address)
+            self._test_create_ssl_connection(httpd, create_connection)
 
     @unittest.skipIf(ssl is None, 'No ssl module')
     @unittest.skipUnless(hasattr(socket, 'AF_UNIX'), 'No UNIX Sockets')
@@ -624,13 +652,13 @@ class EventLoopTestsMixin:
         check_sockname = not osx_tiger()
 
         with test_utils.run_test_unix_server(use_ssl=True) as httpd:
-            conn_fut = self.loop.create_unix_connection(
-                lambda: MyProto(loop=self.loop),
-                httpd.address,
-                ssl=test_utils.dummy_ssl_context(),
+            create_connection = functools.partial(
+                self.loop.create_unix_connection,
+                lambda: MyProto(loop=self.loop), httpd.address,
                 server_hostname='127.0.0.1')
 
-            self._basetest_create_ssl_connection(conn_fut, check_sockname)
+            self._test_create_ssl_connection(httpd, create_connection,
+                                             check_sockname)
 
     def test_create_connection_local_addr(self):
         with test_utils.run_test_server() as httpd:
index e5030cd5d81b8da4bd0dabfc320a1dd646682cbc..fd8011e298060109adf8a2055cdee94fa82d4abc 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -27,6 +27,9 @@ Core and Builtins
 Library
 -------
 
+- Issue #22641: In asyncio, the default SSL context for client connections
+  is now created using ssl.create_default_context(), for stronger security.
+
 - Issue #22435: Fix a file descriptor leak when SocketServer bind fails.
 
 - Issue #13096: Fixed segfault in CTypes POINTER handling of large