[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

AltStyle によって変換されたページ (->オリジナル) /