From d3a4d12c92de6a43f9f067ac45f3f2e44d6dd652 Mon Sep 17 00:00:00 2001 From: lochiiconnectivity Date: Mon, 27 Jun 2016 18:45:31 +0000 Subject: [PATCH] [LIBCLOUD-827] Add support for SNI --- libcloud/httplib_ssl.py | 52 +++++++++++++++++++++++++++++---------- libcloud/test/test_httplib_ssl.py | 14 +++++++++-- 2 files changed, 51 insertions(+), 15 deletions(-) diff --git a/libcloud/httplib_ssl.py b/libcloud/httplib_ssl.py index b3e3101..cc927fe 100644 --- a/libcloud/httplib_ssl.py +++ b/libcloud/httplib_ssl.py @@ -292,21 +292,47 @@ class LibcloudHTTPSConnection(httplib.HTTPSConnection, LibcloudBaseConnection): ssl_version = libcloud.security.SSL_VERSION - try: - self.sock = ssl.wrap_socket( - sock, - self.key_file, - self.cert_file, - cert_reqs=ssl.CERT_REQUIRED, - ca_certs=self.ca_cert, - ssl_version=ssl_version) - except socket.error: - exc = sys.exc_info()[1] - # Re-throw an exception with a more friendly error message - exc = get_socket_error_exception(ssl_version=ssl_version, exc=exc) - raise exc + # If we support SNI, use SSLContext and tls_context.wrap_socket() + # else revert to older behaviour with ssl.wrap_socket() + + if getattr(ssl, 'HAS_SNI', False): + self.tls_context = ssl.SSLContext(ssl_version) + self.tls_context.verify_mode = ssl.CERT_REQUIRED + if self.cert_file and self.key_file: + self.tls_context.load_cert_chain( + self.cert_file, self.key_file, None) + if self.ca_cert: + self.tls_context.load_verify_locations(self.ca_cert) + try: + self.sock = self.tls_context.wrap_socket( + sock, + server_hostname=self.host, + ) + except: + exc = sys.exc_info()[1] + exc = get_socket_error_exception(ssl_version=ssl_version, + exc=exc) + raise exc + else: + try: + self.sock = ssl.wrap_socket( + sock, + self.key_file, + self.cert_file, + cert_reqs=ssl.CERT_REQUIRED, + ca_certs=self.ca_cert, + ssl_version=ssl_version + ) + except: + exc = sys.exc_info()[1] + exc = get_socket_error_exception(ssl_version=ssl_version, + exc=exc) + raise exc cert = self.sock.getpeercert() + + # Verify Hostname + try: match_hostname(cert, self.host) except CertificateError: diff --git a/libcloud/test/test_httplib_ssl.py b/libcloud/test/test_httplib_ssl.py index 996498f..38f6ac8 100644 --- a/libcloud/test/test_httplib_ssl.py +++ b/libcloud/test/test_httplib_ssl.py @@ -16,6 +16,7 @@ import os import sys import os.path +import ssl import socket import mock @@ -109,8 +110,17 @@ class TestHttpLibSSLTests(unittest.TestCase): @mock.patch('socket.create_connection', mock.MagicMock()) @mock.patch('socket.socket', mock.MagicMock()) - @mock.patch('ssl.wrap_socket') - def test_connect_throws_friendly_error_message_on_ssl_wrap_connection_reset_by_peer(self, mock_wrap_socket): + def test_connect_throws_friendly_error_message_on_ssl_wrap_connection_reset_by_peer(self): + + mock_wrap_socket = None + + if getattr(ssl, 'HAS_SNI', False): + ssl.SSLContext.wrap_socket = mock.MagicMock() + mock_wrap_socket = ssl.SSLContext.wrap_socket + else: + ssl.wrap_socket = mock.MagicMock() + mock_wrap_socket = ssl.wrap_socket + # Test that we re-throw a more friendly error message in case # "connection reset by peer" error occurs when trying to establish a # SSL connection -- 1.9.1