Skip to content

Commit cdc6fbf

Browse files
committed
Add ssl_context to TCPConnector
1 parent f1d94c7 commit cdc6fbf

File tree

2 files changed

+53
-6
lines changed

2 files changed

+53
-6
lines changed

aiohttp/connector.py

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -223,10 +223,17 @@ class TCPConnector(BaseConnector):
223223
"""
224224

225225
def __init__(self, *args, verify_ssl=True,
226-
resolve=False, family=socket.AF_INET, **kwargs):
226+
resolve=False, family=socket.AF_INET, ssl_context=None,
227+
**kwargs):
228+
if not verify_ssl and ssl_context is not None:
229+
raise ValueError(
230+
"Either disable ssl certificate validation by "
231+
"verify_ssl=False or specify ssl_context, not both.")
232+
227233
super().__init__(*args, **kwargs)
228234

229235
self._verify_ssl = verify_ssl
236+
self._ssl_context = ssl_context
230237
self._family = family
231238
self._resolve = resolve
232239
self._resolved_hosts = {}
@@ -236,6 +243,30 @@ def verify_ssl(self):
236243
"""Do check for ssl certifications?"""
237244
return self._verify_ssl
238245

246+
@property
247+
def ssl_context(self):
248+
"""SSLContext instance for https requests.
249+
250+
Lazy property, creates context on demand.
251+
"""
252+
if self._ssl_context is None:
253+
if not self._verify_ssl:
254+
sslcontext = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
255+
sslcontext.options |= ssl.OP_NO_SSLv2
256+
sslcontext.set_default_verify_paths()
257+
elif hasattr(ssl, 'create_default_context'):
258+
# Python 3.4+
259+
sslcontext = ssl.create_default_context()
260+
else: # pragma: no cover
261+
# Fallback for Python 3.3.
262+
sslcontext = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
263+
sslcontext.options |= ssl.OP_NO_SSLv2
264+
sslcontext.options |= ssl.OP_NO_SSLv3
265+
sslcontext.set_default_verify_paths()
266+
sslcontext.verify_mode = ssl.CERT_REQUIRED
267+
self._ssl_context = sslcontext
268+
return self._ssl_context
269+
239270
@property
240271
def family(self):
241272
"""Socket family like AF_INET."""
@@ -288,11 +319,10 @@ def _create_connection(self, req, **kwargs):
288319
289320
Has same keyword arguments as BaseEventLoop.create_connection.
290321
"""
291-
sslcontext = req.ssl
292-
if req.ssl and not self._verify_ssl:
293-
sslcontext = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
294-
sslcontext.options |= ssl.OP_NO_SSLv2
295-
sslcontext.set_default_verify_paths()
322+
if req.ssl:
323+
sslcontext = self.ssl_context
324+
else:
325+
sslcontext = None
296326

297327
hosts = yield from self._resolve_host(req.host, req.port)
298328

tests/test_connector.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import time
77
import socket
88
import unittest
9+
import ssl
910
from unittest import mock
1011

1112
import aiohttp
@@ -312,6 +313,22 @@ def test_tcp_connector_clear_resolved_hosts(self):
312313
conn.clear_resolved_hosts()
313314
self.assertEqual(conn.resolved_hosts, {})
314315

316+
def test_ambigous_verify_ssl_and_ssl_context(self):
317+
with self.assertRaises(ValueError):
318+
aiohttp.TCPConnector(
319+
verify_ssl=False,
320+
ssl_context=ssl.SSLContext(ssl.PROTOCOL_SSLv23))
321+
322+
def test_dont_recreate_ssl_context(self):
323+
conn = aiohttp.TCPConnector(loop=self.loop)
324+
ctx = conn.ssl_context
325+
self.assertIs(ctx, conn.ssl_context)
326+
327+
def test_respect_precreated_ssl_context(self):
328+
ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
329+
conn = aiohttp.TCPConnector(loop=self.loop, ssl_context=ctx)
330+
self.assertIs(ctx, conn.ssl_context)
331+
315332

316333
class HttpClientConnectorTests(unittest.TestCase):
317334

0 commit comments

Comments
 (0)