Skip to content

Commit

Permalink
Merge pull request #1 from infoscout/fix/ca-5976/s3-dottedbuckets
Browse files Browse the repository at this point in the history
CA-5976: Disable SSL validation
  • Loading branch information
Dana Ford authored May 10, 2017
2 parents 315b76e + b9938e0 commit 232fa21
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 14 deletions.
2 changes: 1 addition & 1 deletion boto/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
from boto.compat import urlparse
from boto.exception import InvalidUriError

__version__ = '2.46.1'
__version__ = '2.46.1.1'
Version = __version__ # for backware compatibility

# http://bugs.python.org/issue7980
Expand Down
14 changes: 14 additions & 0 deletions boto/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,17 @@ def parse_qs_safe(qs, keep_blank_values=False, strict_parsing=False,
result[decoded_name] = decoded_value
return result
return qs_dict


# Create a wrapper function to get a non-validating SSL context (boto
# implements its own certificate validation) on Python versions that
# have that concept.
try:
from ssl import create_default_context as _create_default_context
def create_non_validating_ssl_context(cafile=None):
ctx = _create_default_context(cafile=cafile)
ctx.check_hostname = False
return ctx
except ImportError:
def create_non_validating_ssl_context(cafile=None):
return None
15 changes: 9 additions & 6 deletions boto/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -747,13 +747,16 @@ def new_http_connection(self, host, port, is_secure):
connection = self.proxy_ssl(host, is_secure and 443 or 80)
elif self.https_connection_factory:
connection = self.https_connection_factory(host)
elif self.https_validate_certificates and HAVE_HTTPS_CONNECTION:
connection = https_connection.CertValidatingHTTPSConnection(
host, ca_certs=self.ca_certificates_file,
**http_connection_kwargs)
elif HAVE_HTTPS_CONNECTION:
if self.https_validate_certificates:
conn_cls = https_connection.CertValidatingHTTPSConnection
else:
conn_cls = https_connection.NonCertValidatingHTTPSConnection

http_connection_kwargs["ca_certs"] = self.ca_certificates_file
connection = conn_cls(host, **http_connection_kwargs)
else:
connection = http_client.HTTPSConnection(
host, **http_connection_kwargs)
connection = http_client.HTTPSConnection(host, **http_connection_kwargs)
else:
boto.log.debug('establishing HTTP connection: kwargs=%s' %
http_connection_kwargs)
Expand Down
49 changes: 42 additions & 7 deletions boto/https_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

import boto

from boto.compat import six, http_client
from boto.compat import six, http_client, create_non_validating_ssl_context


class InvalidCertificateException(http_client.HTTPException):
Expand Down Expand Up @@ -83,8 +83,24 @@ def ValidateCertificateHostname(cert, hostname):
return False


class CertValidatingHTTPSConnection(http_client.HTTPConnection):
"""An HTTPConnection that connects over SSL and validates certificates."""
class NonCertValidatingHTTPSConnection(http_client.HTTPConnection):
"""
An HTTPConnection that connects over SSL and does not validate certificates against
the server's hostname.
Note that "NonCertValidating" isn't totally accurate. This implementation does validate
that the certificate is valid, only it doesn't validate that it actually matches the
host we are connecting to!
We provide our own implementation instead of http_client.HTTPSConnection to have
control over certificate validation, because the behavior in http_client.HTTPSConneciton
varies greatly across supported versions of Python:
+ 2.6 -> 2.7.8: those don't validate anything, ever.
+ 2.7.9: leaves it up to the SSL context to validate.
+ 3.x: leaves it up to the SSL context, but also requires
`check_hostname=False` to actually skip SSL validation.
"""

default_port = http_client.HTTPS_PORT

Expand Down Expand Up @@ -125,10 +141,29 @@ 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)

ctx = create_non_validating_ssl_context(cafile=self.ca_certs)
if ctx is not None:
if self.cert_file is not None:
ctx.load_cert_chain(certfile=self.cert_file, keyfile=self.key_file)
wrap_socket = ctx.wrap_socket
else:
wrap_socket = lambda s: ssl.wrap_socket(s, keyfile=self.key_file,
certfile=self.cert_file,
cert_reqs=ssl.CERT_REQUIRED,
ca_certs=self.ca_certs)
self.sock = wrap_socket(sock)


class CertValidatingHTTPSConnection(NonCertValidatingHTTPSConnection):
"""
An HTTPConnection that connects over SSL and validates certificates against
the server's hostname.
"""

def connect(self):
NonCertValidatingHTTPSConnection.connect(self)

cert = self.sock.getpeercert()
hostname = self.host.split(':', 0)[0]
if not ValidateCertificateHostname(cert, hostname):
Expand Down
7 changes: 7 additions & 0 deletions tests/integration/s3/test_cert_verification.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from tests.integration import ServiceCertVerificationTest

import boto.s3
import boto.s3.connection


class S3CertVerificationTest(unittest.TestCase, ServiceCertVerificationTest):
Expand All @@ -37,3 +38,9 @@ class S3CertVerificationTest(unittest.TestCase, ServiceCertVerificationTest):

def sample_service_call(self, conn):
conn.get_all_buckets()
try:
conn.get_bucket("bucket.with.periods")
except boto.s3.connection.S3ResponseError:
# This may be a 403 or a 404, we don't care. What matters is that we
# don't get a certificate error.
pass

0 comments on commit 232fa21

Please sign in to comment.