Skip to content

Commit

Permalink
Support a servername parameter on HTTPSConnections which overrides th…
Browse files Browse the repository at this point in the history
…e name used for SNI/hostname verification.
  • Loading branch information
JackOfMostTrades committed Jun 15, 2018
1 parent 6be6372 commit f3ff97b
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 3 deletions.
3 changes: 3 additions & 0 deletions CHANGES.rst
Expand Up @@ -6,6 +6,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
17 changes: 17 additions & 0 deletions test/with_dummyserver/test_https.py
Expand Up @@ -323,6 +323,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
12 changes: 9 additions & 3 deletions 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

0 comments on commit f3ff97b

Please sign in to comment.