Skip to content

Commit

Permalink
Send SNI for HTTPS connections
Browse files Browse the repository at this point in the history
Several Google servers, including storage.googleapis.com, send an
invalid certificate in response to HTTPS connections which do not
include Server Name Indication (SNI) in ClientHello:

    OU = "No SNI provided; please fix your client.", CN = invalid2.invalid

This can be demonstrated by running
`openssl s_client -connect storage.googleapis.com:443` with and without
the `-noservername` option.  This causes errors in boto, such as:

    Traceback (most recent call last):
      File "./gsbototest.py", line 6, in <module>
        boto.storage_uri('bucket-name', 'gs').create_bucket()
      File "/tmp/boto/boto/storage_uri.py", line 574, in create_bucket
        storage_class)
      File "/tmp/boto/boto/gs/connection.py", line 95, in create_bucket
        data=get_utf8_value(data))
      File "/tmp/boto/boto/s3/connection.py", line 682, in make_request
        retry_handler=retry_handler
      File "/tmp/boto/boto/connection.py", line 1074, in make_request
        retry_handler=retry_handler)
      File "/tmp/boto/boto/connection.py", line 1033, in _mexe
        raise ex
    ssl.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:727)

To fix the issue, this commit sends SNI, where possible.  This requires
calling wrap_socket on an SSLContext instance.  The necessary SSLContext
is constructed and additional SSLSocket attributes are set using the
[same code as SSLSocket] to minimize potential differences introduced.
The code is only called when SSLContext is available (Python 2.7.9 and
later) and only when OpenSSL is compiled with SNI support.

[same code as SSLSocket]: https://github.com/python/cpython/blob/v2.7.15/Lib/ssl.py#L555-L570

Signed-off-by: Kevin Locke <kevin@kevinlocke.name>
  • Loading branch information
kevinoid committed Jan 16, 2019
1 parent eca5f98 commit 79142a0
Showing 1 changed file with 22 additions and 4 deletions.
26 changes: 22 additions & 4 deletions boto/https_connection.py
Expand Up @@ -125,10 +125,28 @@ def connect(self):
else:
msg += "using system provided SSL certs"
boto.log.debug(msg)
self.sock = ssl.wrap_socket(sock, keyfile=self.key_file,
certfile=self.cert_file,
cert_reqs=ssl.CERT_REQUIRED,
ca_certs=self.ca_certs)
if hasattr(ssl, 'SSLContext') and getattr(ssl, 'HAS_SNI', False):
# Use SSLContext so we can specify server_hostname for SNI
# (Required for connections to storage.googleapis.com)
context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
context.verify_mode = ssl.CERT_REQUIRED
if self.ca_certs:
context.load_verify_locations(self.ca_certs)
if self.cert_file:
context.load_cert_chain(self.cert_file, self.key_file)
self.sock = context.wrap_socket(sock, server_hostname=self.host)

This comment has been minimized.

Copy link
@houglum

houglum Mar 19, 2019

Contributor

@kevinoid It looks like this doesn't work on Python 3.7.

Found this because we tried to add Python 3.7 to Boto's list of TravisCI test environments and got the errors below:
https://travis-ci.org/boto/boto/jobs/508512525

This comment has been minimized.

Copy link
@houglum

houglum Mar 19, 2019

Contributor

@kevinoid Nevermind. It was just failing in tests due to the silly subclass requirements added in the ssl module in Py 3.7. Turns out, FakeSSLSocket isn't a subclass of SSLSocket. Somebody hit the same problem here, and even linked their fix: gabrielfalcao/HTTPretty#337

So nevermind :)

This comment has been minimized.

Copy link
@kevinoid

kevinoid Mar 20, 2019

Author Contributor

Good find @houglum! Thanks for tracking it down. I had bisected the problem to python/cpython@4df60f18c6 but hadn't figured out why before getting pulled away.

Thanks for keeping me in the loop!

# Add attributes only set in SSLSocket constructor without context:
self.sock.keyfile = self.key_file
self.sock.certfile = self.cert_file
self.sock.cert_reqs = context.verify_mode
self.sock.ssl_version = ssl.PROTOCOL_SSLv23
self.sock.ca_certs = self.ca_certs
self.sock.ciphers = None
else:
self.sock = ssl.wrap_socket(sock, keyfile=self.key_file,
certfile=self.cert_file,
cert_reqs=ssl.CERT_REQUIRED,
ca_certs=self.ca_certs)
cert = self.sock.getpeercert()
hostname = self.host.split(':', 0)[0]
if not ValidateCertificateHostname(cert, hostname):
Expand Down

0 comments on commit 79142a0

Please sign in to comment.