[Python-checkins] cpython (3.4): Issue #25114, asyncio: add ssl_object extra info to SSL transports

victor.stinner python-checkins at python.org
Mon Sep 21 18:09:41 CEST 2015


https://hg.python.org/cpython/rev/d7859e7e7071
changeset:   98133:d7859e7e7071
branch:      3.4
parent:      98128:f13a5b5a2824
user:        Victor Stinner <victor.stinner at gmail.com>
date:        Mon Sep 21 18:06:17 2015 +0200
summary:
  Issue #25114, asyncio: add ssl_object extra info to SSL transports

This info is required on Python 3.5 and newer to get specific information on
the SSL object, like getting the binary peer certificate (instead of getting
it as text).

files:
  Doc/library/asyncio-protocol.rst     |   5 +
  Lib/asyncio/selector_events.py       |   1 +
  Lib/asyncio/sslproto.py              |   4 +
  Lib/test/test_asyncio/test_events.py |  75 +++++++++++++--
  4 files changed, 73 insertions(+), 12 deletions(-)


diff --git a/Doc/library/asyncio-protocol.rst b/Doc/library/asyncio-protocol.rst
--- a/Doc/library/asyncio-protocol.rst
+++ b/Doc/library/asyncio-protocol.rst
@@ -71,6 +71,8 @@
         - ``'peercert'``: peer certificate; result of
           :meth:`ssl.SSLSocket.getpeercert`
         - ``'sslcontext'``: :class:`ssl.SSLContext` instance
+        - ``'ssl_object'``: :class:`ssl.SSLObject` or :class:`ssl.SSLSocket`
+          instance
 
       * pipe:
 
@@ -80,6 +82,9 @@
 
         - ``'subprocess'``: :class:`subprocess.Popen` instance
 
+   .. versionchanged:: 3.4.4
+      ``'ssl_object'`` info was added to SSL sockets.
+
 
 ReadTransport
 -------------
diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py
--- a/Lib/asyncio/selector_events.py
+++ b/Lib/asyncio/selector_events.py
@@ -843,6 +843,7 @@
         self._extra.update(peercert=peercert,
                            cipher=self._sock.cipher(),
                            compression=self._sock.compression(),
+                           ssl_object=self._sock,
                            )
 
         self._read_wants_write = False
diff --git a/Lib/asyncio/sslproto.py b/Lib/asyncio/sslproto.py
--- a/Lib/asyncio/sslproto.py
+++ b/Lib/asyncio/sslproto.py
@@ -295,6 +295,7 @@
 
     def __init__(self, loop, ssl_protocol, app_protocol):
         self._loop = loop
+        # SSLProtocol instance
         self._ssl_protocol = ssl_protocol
         self._app_protocol = app_protocol
         self._closed = False
@@ -425,10 +426,12 @@
         self._app_protocol = app_protocol
         self._app_transport = _SSLProtocolTransport(self._loop,
                                                     self, self._app_protocol)
+        # _SSLPipe instance (None until the connection is made)
         self._sslpipe = None
         self._session_established = False
         self._in_handshake = False
         self._in_shutdown = False
+        # transport, ex: SelectorSocketTransport
         self._transport = None
 
     def _wakeup_waiter(self, exc=None):
@@ -591,6 +594,7 @@
         self._extra.update(peercert=peercert,
                            cipher=sslobj.cipher(),
                            compression=sslobj.compression(),
+                           ssl_object=sslobj,
                            )
         self._app_protocol.connection_made(self._app_transport)
         self._wakeup_waiter()
diff --git a/Lib/test/test_asyncio/test_events.py b/Lib/test/test_asyncio/test_events.py
--- a/Lib/test/test_asyncio/test_events.py
+++ b/Lib/test/test_asyncio/test_events.py
@@ -57,6 +57,17 @@
 ONLYKEY = data_file('ssl_key.pem')
 SIGNED_CERTFILE = data_file('keycert3.pem')
 SIGNING_CA = data_file('pycacert.pem')
+PEERCERT = {'serialNumber': 'B09264B1F2DA21D1',
+            'version': 1,
+            'subject': ((('countryName', 'XY'),),
+                    (('localityName', 'Castle Anthrax'),),
+                    (('organizationName', 'Python Software Foundation'),),
+                    (('commonName', 'localhost'),)),
+            'issuer': ((('countryName', 'XY'),),
+                    (('organizationName', 'Python Software Foundation CA'),),
+                    (('commonName', 'our-ca-server'),)),
+            'notAfter': 'Nov 13 19:47:07 2022 GMT',
+            'notBefore': 'Jan  4 19:47:07 2013 GMT'}
 
 
 class MyBaseProto(asyncio.Protocol):
@@ -596,22 +607,56 @@
             self.assertGreater(pr.nbytes, 0)
             tr.close()
 
+    def check_ssl_extra_info(self, client, check_sockname=True,
+                             peername=None, peercert={}):
+        if check_sockname:
+            self.assertIsNotNone(client.get_extra_info('sockname'))
+        if peername:
+            self.assertEqual(peername,
+                             client.get_extra_info('peername'))
+        else:
+            self.assertIsNotNone(client.get_extra_info('peername'))
+        self.assertEqual(peercert,
+                         client.get_extra_info('peercert'))
+
+        # Python disables compression to prevent CRIME attacks by default
+        self.assertIsNone(client.get_extra_info('compression'))
+
+        # test SSL cipher
+        cipher = client.get_extra_info('cipher')
+        self.assertIsInstance(cipher, tuple)
+        self.assertEqual(len(cipher), 3, cipher)
+        self.assertIsInstance(cipher[0], str)
+        self.assertIsInstance(cipher[1], str)
+        self.assertIsInstance(cipher[2], int)
+
+        # test SSL object
+        sslobj = client.get_extra_info('ssl_object')
+        self.assertIsNotNone(sslobj)
+        self.assertEqual(sslobj.compression(),
+                         client.get_extra_info('compression'))
+        self.assertEqual(sslobj.cipher(),
+                         client.get_extra_info('cipher'))
+        self.assertEqual(sslobj.getpeercert(),
+                         client.get_extra_info('peercert'))
+
     def _basetest_create_ssl_connection(self, connection_fut,
-                                        check_sockname=True):
+                                        check_sockname=True,
+                                        peername=None):
         tr, pr = self.loop.run_until_complete(connection_fut)
         self.assertIsInstance(tr, asyncio.Transport)
         self.assertIsInstance(pr, asyncio.Protocol)
         self.assertTrue('ssl' in tr.__class__.__name__.lower())
-        if check_sockname:
-            self.assertIsNotNone(tr.get_extra_info('sockname'))
+        self.check_ssl_extra_info(tr, check_sockname, peername)
         self.loop.run_until_complete(pr.done)
         self.assertGreater(pr.nbytes, 0)
         tr.close()
 
     def _test_create_ssl_connection(self, httpd, create_connection,
-                                    check_sockname=True):
+                                    check_sockname=True, peername=None):
         conn_fut = create_connection(ssl=test_utils.dummy_ssl_context())
-        self._basetest_create_ssl_connection(conn_fut, check_sockname)
+        self._basetest_create_ssl_connection(conn_fut, check_sockname,
+                                             peername)
 
         # ssl.Purpose was introduced in Python 3.4
         if hasattr(ssl, 'Purpose'):
@@ -629,7 +674,8 @@
             with mock.patch('ssl.create_default_context',
                             side_effect=_dummy_ssl_create_context) as m:
                 conn_fut = create_connection(ssl=True)
-                self._basetest_create_ssl_connection(conn_fut, check_sockname)
+                self._basetest_create_ssl_connection(conn_fut, check_sockname,
+                                                     peername)
                 self.assertEqual(m.call_count, 1)
 
         # With the real ssl.create_default_context(), certificate
@@ -638,7 +684,8 @@
             conn_fut = create_connection(ssl=True)
             # Ignore the "SSL handshake failed" log in debug mode
             with test_utils.disable_logger():
-                self._basetest_create_ssl_connection(conn_fut, check_sockname)
+                self._basetest_create_ssl_connection(conn_fut, check_sockname,
+                                                     peername)
 
         self.assertEqual(cm.exception.reason, 'CERTIFICATE_VERIFY_FAILED')
 
@@ -649,7 +696,8 @@
                 self.loop.create_connection,
                 lambda: MyProto(loop=self.loop),
                 *httpd.address)
-            self._test_create_ssl_connection(httpd, create_connection)
+            self._test_create_ssl_connection(httpd, create_connection,
+                                             peername=httpd.address)
 
     def test_legacy_create_ssl_connection(self):
         with test_utils.force_legacy_ssl_support():
@@ -669,7 +717,8 @@
                 server_hostname='127.0.0.1')
 
             self._test_create_ssl_connection(httpd, create_connection,
-                                             check_sockname)
+                                             check_sockname,
+                                             peername=httpd.address)
 
     def test_legacy_create_ssl_unix_connection(self):
         with test_utils.force_legacy_ssl_support():
@@ -819,9 +868,7 @@
         self.assertEqual(3, proto.nbytes)
 
         # extra info is available
-        self.assertIsNotNone(proto.transport.get_extra_info('sockname'))
-        self.assertEqual('127.0.0.1',
-                         proto.transport.get_extra_info('peername')[0])
+        self.check_ssl_extra_info(client, peername=(host, port))
 
         # close connection
         proto.transport.close()
@@ -1023,6 +1070,10 @@
                                           server_hostname='localhost')
         client, pr = self.loop.run_until_complete(f_c)
 
+        # extra info is available
+        self.check_ssl_extra_info(client,peername=(host, port),
+                                  peercert=PEERCERT)
+
         # close connection
         proto.transport.close()
         client.close()

-- 
Repository URL: https://hg.python.org/cpython


More information about the Python-checkins mailing list