Skip to content

Commit 980c0d0

Browse files
committed
Merge pull request #211 from KeepSafe/Issue_206_ssl_context
Add ssl_context to TCPConnector
2 parents ec62dc5 + 4511918 commit 980c0d0

File tree

2 files changed

+56
-6
lines changed

2 files changed

+56
-6
lines changed

aiohttp/connector.py

Lines changed: 39 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,33 @@ 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.options |= ssl.OP_NO_SSLv3
257+
sslcontext.options |= getattr(ssl, "OP_NO_COMPRESSION", 0)
258+
sslcontext.set_default_verify_paths()
259+
elif hasattr(ssl, 'create_default_context'):
260+
# Python 3.4+
261+
sslcontext = ssl.create_default_context()
262+
else: # pragma: no cover
263+
# Fallback for Python 3.3.
264+
sslcontext = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
265+
sslcontext.options |= ssl.OP_NO_SSLv2
266+
sslcontext.options |= ssl.OP_NO_SSLv3
267+
sslcontext.options |= getattr(ssl, "OP_NO_COMPRESSION", 0)
268+
sslcontext.set_default_verify_paths()
269+
sslcontext.verify_mode = ssl.CERT_REQUIRED
270+
self._ssl_context = sslcontext
271+
return self._ssl_context
272+
239273
@property
240274
def family(self):
241275
"""Socket family like AF_INET."""
@@ -288,11 +322,10 @@ def _create_connection(self, req, **kwargs):
288322
289323
Has same keyword arguments as BaseEventLoop.create_connection.
290324
"""
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()
325+
if req.ssl:
326+
sslcontext = self.ssl_context
327+
else:
328+
sslcontext = None
296329

297330
hosts = yield from self._resolve_host(req.host, req.port)
298331

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)