Skip to content
This repository was archived by the owner on Jan 13, 2021. It is now read-only.

Commit 1b3c989

Browse files
committed
Merge pull request #129 from jdecuyper/provide-external-SSLContext
Support external SSLContext
2 parents 392c2c2 + 2872fde commit 1b3c989

File tree

9 files changed

+95
-16
lines changed

9 files changed

+95
-16
lines changed

docs/source/api.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ Headers
4040
.. autoclass:: hyper.common.headers.HTTPHeaderMap
4141
:inherited-members:
4242

43+
SSLContext
44+
----------
45+
46+
.. automethod:: hyper.tls.init_context
47+
4348
Requests Transport Adapter
4449
--------------------------
4550

hyper/common/connection.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,20 +39,24 @@ class HTTPConnection(object):
3939
:param enable_push: (optional) Whether the server is allowed to push
4040
resources to the client (see
4141
:meth:`get_pushes() <hyper.HTTP20Connection.get_pushes>`).
42+
:param ssl_context: (optional) A class with custom certificate settings.
43+
If not provided then hyper's default ``SSLContext`` is used instead.
4244
"""
4345
def __init__(self,
4446
host,
4547
port=None,
4648
secure=None,
4749
window_manager=None,
4850
enable_push=False,
51+
ssl_context=None,
4952
**kwargs):
5053

5154
self._host = host
5255
self._port = port
53-
self._h1_kwargs = {'secure': secure}
56+
self._h1_kwargs = {'secure': secure, 'ssl_context': ssl_context}
5457
self._h2_kwargs = {
55-
'window_manager': window_manager, 'enable_push': enable_push
58+
'window_manager': window_manager, 'enable_push': enable_push,
59+
'ssl_context': ssl_context
5660
}
5761

5862
# Add any unexpected kwargs to both dictionaries.

hyper/http11/connection.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,11 @@ class HTTP11Connection(object):
4343
:param secure: (optional) Whether the request should use TLS. Defaults to
4444
``False`` for most requests, but to ``True`` for any request issued to
4545
port 443.
46+
:param ssl_context: (optional) A class with custom certificate settings.
47+
If not provided then hyper's default ``SSLContext`` is used instead.
4648
"""
47-
def __init__(self, host, port=None, secure=None, **kwargs):
49+
def __init__(self, host, port=None, secure=None, ssl_context=None,
50+
**kwargs):
4851
if port is None:
4952
try:
5053
self.host, self.port = host.split(':')
@@ -64,6 +67,7 @@ def __init__(self, host, port=None, secure=None, **kwargs):
6467
else:
6568
self.secure = False
6669

70+
self.ssl_context = ssl_context
6771
self._sock = None
6872

6973
#: The size of the in-memory buffer used to store data from the
@@ -88,7 +92,7 @@ def connect(self):
8892
proto = None
8993

9094
if self.secure:
91-
sock, proto = wrap_socket(sock, self.host)
95+
sock, proto = wrap_socket(sock, self.host, self.ssl_context)
9296

9397
log.debug("Selected NPN protocol: %s", proto)
9498
sock = BufferedSocket(sock, self.network_buffer_size)

hyper/http20/connection.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,11 @@ class HTTP20Connection(object):
5151
:param enable_push: (optional) Whether the server is allowed to push
5252
resources to the client (see
5353
:meth:`get_pushes() <hyper.HTTP20Connection.get_pushes>`).
54+
:param ssl_context: (optional) A class with custom certificate settings.
55+
If not provided then hyper's default ``SSLContext`` is used instead.
5456
"""
5557
def __init__(self, host, port=None, window_manager=None, enable_push=False,
56-
**kwargs):
58+
ssl_context=None, **kwargs):
5759
"""
5860
Creates an HTTP/2 connection to a specific server.
5961
"""
@@ -67,6 +69,7 @@ def __init__(self, host, port=None, window_manager=None, enable_push=False,
6769
self.host, self.port = host, port
6870

6971
self._enable_push = enable_push
72+
self.ssl_context = ssl_context
7073

7174
#: The size of the in-memory buffer used to store data from the
7275
#: network. This is used as a performance optimisation. Increase buffer
@@ -206,7 +209,7 @@ def connect(self):
206209
if self._sock is None:
207210
sock = socket.create_connection((self.host, self.port), 5)
208211

209-
sock, proto = wrap_socket(sock, self.host)
212+
sock, proto = wrap_socket(sock, self.host, self.ssl_context)
210213
log.debug("Selected NPN protocol: %s", proto)
211214
assert proto in H2_NPN_PROTOCOLS
212215

hyper/tls.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,22 @@
2323
cert_loc = path.join(path.dirname(__file__), 'certs.pem')
2424

2525

26-
def wrap_socket(sock, server_hostname):
26+
def wrap_socket(sock, server_hostname, ssl_context=None):
2727
"""
2828
A vastly simplified SSL wrapping function. We'll probably extend this to
2929
do more things later.
3030
"""
3131
global _context
3232

33+
# create the singleton SSLContext we use
3334
if _context is None: # pragma: no cover
34-
_context = _init_context()
35+
_context = init_context()
36+
37+
# if an SSLContext is provided then use it instead of default context
38+
_ssl_context = ssl_context or _context
3539

3640
# the spec requires SNI support
37-
ssl_sock = _context.wrap_socket(sock, server_hostname=server_hostname)
41+
ssl_sock = _ssl_context.wrap_socket(sock, server_hostname=server_hostname)
3842
# Setting SSLContext.check_hostname to True only verifies that the
3943
# post-handshake servername matches that of the certificate. We also need
4044
# to check that it matches the requested one.
@@ -58,13 +62,21 @@ def wrap_socket(sock, server_hostname):
5862
return (ssl_sock, proto)
5963

6064

61-
def _init_context():
65+
def init_context(cert_path=None):
6266
"""
63-
Creates the singleton SSLContext we use.
67+
Create a new ``SSLContext`` that is correctly set up for an HTTP/2 connection.
68+
This SSL context object can be customized and passed as a parameter to the
69+
:class:`HTTPConnection <hyper.HTTPConnection>` class. Provide your
70+
own certificate file in case you don’t want to use hyper’s default
71+
certificate. The path to the certificate can be absolute or relative
72+
to your working directory.
73+
74+
:param cert_path: (optional) The path to the certificate file.
75+
:returns: An ``SSLContext`` correctly set up for HTTP/2.
6476
"""
6577
context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
6678
context.set_default_verify_paths()
67-
context.load_verify_locations(cafile=cert_loc)
79+
context.load_verify_locations(cafile=cert_path or cert_loc)
6880
context.verify_mode = ssl.CERT_REQUIRED
6981
context.check_hostname = True
7082

test/test_SSLContext.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
Tests the hyper SSLContext.
4+
"""
5+
import hyper
6+
from hyper.common.connection import HTTPConnection
7+
from hyper.compat import ssl
8+
import pytest
9+
10+
class TestSSLContext(object):
11+
"""
12+
Tests default and custom SSLContext
13+
"""
14+
def test_default_context(self):
15+
# Create default SSLContext
16+
hyper.tls._context = hyper.tls.init_context()
17+
assert hyper.tls._context.check_hostname == True
18+
assert hyper.tls._context.verify_mode == ssl.CERT_REQUIRED
19+
assert hyper.tls._context.options & ssl.OP_NO_COMPRESSION != 0
20+
21+
22+
def test_custom_context(self):
23+
# The following SSLContext doesn't provide any valid certicate.
24+
# Its purpose is only to confirm that hyper is not using its
25+
# default SSLContext.
26+
context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
27+
context.verify_mode = ssl.CERT_NONE
28+
context.check_hostname = False
29+
30+
hyper.tls._context = context
31+
32+
assert hyper.tls._context.check_hostname == False
33+
assert hyper.tls._context.verify_mode == ssl.CERT_NONE
34+
assert hyper.tls._context.options & ssl.OP_NO_COMPRESSION == 0
35+
36+
37+
def test_HTTPConnection_with_custom_context(self):
38+
context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
39+
context.set_default_verify_paths()
40+
context.verify_mode = ssl.CERT_REQUIRED
41+
context.check_hostname = True
42+
context.set_npn_protocols(['h2', 'h2-15'])
43+
context.options |= ssl.OP_NO_COMPRESSION
44+
45+
conn = HTTPConnection('http2bin.org', 443, ssl_context=context)
46+
47+
assert conn.ssl_context.check_hostname == True
48+
assert conn.ssl_context.verify_mode == ssl.CERT_REQUIRED
49+
assert conn.ssl_context.options & ssl.OP_NO_COMPRESSION != 0

test/test_abstraction.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,25 @@ class TestHTTPConnection(object):
88
def test_h1_kwargs(self):
99
c = HTTPConnection(
1010
'test', 443, secure=False, window_manager=True, enable_push=True,
11-
other_kwarg=True
11+
ssl_context=False, other_kwarg=True
1212
)
1313

1414
assert c._h1_kwargs == {
1515
'secure': False,
16+
'ssl_context': False,
1617
'other_kwarg': True,
1718
}
1819

1920
def test_h2_kwargs(self):
2021
c = HTTPConnection(
2122
'test', 443, secure=False, window_manager=True, enable_push=True,
22-
other_kwarg=True
23+
ssl_context=True, other_kwarg=True
2324
)
2425

2526
assert c._h2_kwargs == {
2627
'window_manager': True,
2728
'enable_push': True,
29+
'ssl_context': True,
2830
'other_kwarg': True,
2931
}
3032

test/test_integration.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727

2828
# Turn off certificate verification for the tests.
2929
if ssl is not None:
30-
hyper.tls._context = hyper.tls._init_context()
30+
hyper.tls._context = hyper.tls.init_context()
3131
hyper.tls._context.check_hostname = False
3232
hyper.tls._context.verify_mode = ssl.CERT_NONE
3333

test/test_integration_http11.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
# Turn off certificate verification for the tests.
1616
if ssl is not None:
17-
hyper.tls._context = hyper.tls._init_context()
17+
hyper.tls._context = hyper.tls.init_context()
1818
hyper.tls._context.check_hostname = False
1919
hyper.tls._context.verify_mode = ssl.CERT_NONE
2020

0 commit comments

Comments
 (0)