Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support a servername parameter on HTTPSConnections #1397

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGES.rst
Expand Up @@ -8,6 +8,9 @@ dev (master)

* ... [Short description of non-trivial change.] (Issue #)

* Add a server_hostname parameter to HTTPSConnection which allows for
overriding the SNI hostname sent in the handshake. (Pull #1397)


1.23 (2018-06-05)
-----------------
Expand Down
12 changes: 9 additions & 3 deletions src/urllib3/connection.py
Expand Up @@ -242,14 +242,15 @@ class HTTPSConnection(HTTPConnection):

def __init__(self, host, port=None, key_file=None, cert_file=None,
strict=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
ssl_context=None, **kw):
ssl_context=None, server_hostname=None, **kw):

HTTPConnection.__init__(self, host, port, strict=strict,
timeout=timeout, **kw)

self.key_file = key_file
self.cert_file = cert_file
self.ssl_context = ssl_context
self.server_hostname = server_hostname

# Required property for Google AppEngine 1.9.0 which otherwise causes
# HTTPS requests to go out as HTTP. (See Issue #356)
Expand All @@ -270,6 +271,7 @@ def connect(self):
keyfile=self.key_file,
certfile=self.cert_file,
ssl_context=self.ssl_context,
server_hostname=self.server_hostname
)


Expand Down Expand Up @@ -328,6 +330,10 @@ def connect(self):
# Override the host with the one we're requesting data from.
hostname = self._tunnel_host

server_hostname = hostname
if self.server_hostname is not None:
server_hostname = self.server_hostname

is_time_off = datetime.date.today() < RECENT_DATE
if is_time_off:
warnings.warn((
Expand All @@ -352,7 +358,7 @@ def connect(self):
certfile=self.cert_file,
ca_certs=self.ca_certs,
ca_cert_dir=self.ca_cert_dir,
server_hostname=hostname,
server_hostname=server_hostname,
ssl_context=context)

if self.assert_fingerprint:
Expand All @@ -373,7 +379,7 @@ def connect(self):
'for details.)'.format(hostname)),
SubjectAltNameWarning
)
_match_hostname(cert, self.assert_hostname or hostname)
_match_hostname(cert, self.assert_hostname or server_hostname)

self.is_verified = (
context.verify_mode == ssl.CERT_REQUIRED or
Expand Down
17 changes: 17 additions & 0 deletions test/with_dummyserver/test_https.py
Expand Up @@ -324,6 +324,23 @@ def test_assert_specific_hostname(self):
https_pool.assert_hostname = 'localhost'
https_pool.request('GET', '/')

def test_server_hostname(self):
https_pool = HTTPSConnectionPool('127.0.0.1', self.port,
cert_reqs='CERT_REQUIRED',
ca_certs=DEFAULT_CA,
server_hostname='localhost')
self.addCleanup(https_pool.close)

conn = https_pool._new_conn()
conn.request('GET', '/')

# Assert the wrapping socket is using the passed-through SNI name.
# pyopenssl doesn't let you pull the server_hostname back off the
# socket, so only add this assertion if the attribute is there (i.e.
# the python ssl module).
if hasattr(conn.sock, 'server_hostname'):
self.assertEqual(conn.sock.server_hostname, "localhost")

def test_assert_fingerprint_md5(self):
https_pool = HTTPSConnectionPool('localhost', self.port,
cert_reqs='CERT_REQUIRED',
Expand Down