Merge "https: Prevent leaking sockets for some operations"
This commit is contained in:
@@ -52,6 +52,82 @@ except ImportError:
|
|||||||
from glanceclient import exc
|
from glanceclient import exc
|
||||||
|
|
||||||
|
|
||||||
|
def verify_callback(host=None):
|
||||||
|
"""
|
||||||
|
We use a partial around the 'real' verify_callback function
|
||||||
|
so that we can stash the host value without holding a
|
||||||
|
reference on the VerifiedHTTPSConnection.
|
||||||
|
"""
|
||||||
|
def wrapper(connection, x509, errnum,
|
||||||
|
depth, preverify_ok, host=host):
|
||||||
|
return do_verify_callback(connection, x509, errnum,
|
||||||
|
depth, preverify_ok, host=host)
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def do_verify_callback(connection, x509, errnum,
|
||||||
|
depth, preverify_ok, host=None):
|
||||||
|
"""
|
||||||
|
Verify the server's SSL certificate.
|
||||||
|
|
||||||
|
This is a standalone function rather than a method to avoid
|
||||||
|
issues around closing sockets if a reference is held on
|
||||||
|
a VerifiedHTTPSConnection by the callback function.
|
||||||
|
"""
|
||||||
|
if x509.has_expired():
|
||||||
|
msg = "SSL Certificate expired on '%s'" % x509.get_notAfter()
|
||||||
|
raise exc.SSLCertificateError(msg)
|
||||||
|
|
||||||
|
if depth == 0 and preverify_ok:
|
||||||
|
# We verify that the host matches against the last
|
||||||
|
# certificate in the chain
|
||||||
|
return host_matches_cert(host, x509)
|
||||||
|
else:
|
||||||
|
# Pass through OpenSSL's default result
|
||||||
|
return preverify_ok
|
||||||
|
|
||||||
|
|
||||||
|
def host_matches_cert(host, x509):
|
||||||
|
"""
|
||||||
|
Verify that the x509 certificate we have received
|
||||||
|
from 'host' correctly identifies the server we are
|
||||||
|
connecting to, ie that the certificate's Common Name
|
||||||
|
or a Subject Alternative Name matches 'host'.
|
||||||
|
"""
|
||||||
|
def check_match(name):
|
||||||
|
# Directly match the name
|
||||||
|
if name == host:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Support single wildcard matching
|
||||||
|
if name.startswith('*.') and host.find('.') > 0:
|
||||||
|
if name[2:] == host.split('.', 1)[1]:
|
||||||
|
return True
|
||||||
|
|
||||||
|
common_name = x509.get_subject().commonName
|
||||||
|
|
||||||
|
# First see if we can match the CN
|
||||||
|
if check_match(common_name):
|
||||||
|
return True
|
||||||
|
# Also try Subject Alternative Names for a match
|
||||||
|
san_list = None
|
||||||
|
for i in range(x509.get_extension_count()):
|
||||||
|
ext = x509.get_extension(i)
|
||||||
|
if ext.get_short_name() == b'subjectAltName':
|
||||||
|
san_list = str(ext)
|
||||||
|
for san in ''.join(san_list.split()).split(','):
|
||||||
|
if san.startswith('DNS:'):
|
||||||
|
if check_match(san.split(':', 1)[1]):
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Server certificate does not match host
|
||||||
|
msg = ('Host "%s" does not match x509 certificate contents: '
|
||||||
|
'CommonName "%s"' % (host, common_name))
|
||||||
|
if san_list is not None:
|
||||||
|
msg = msg + ', subjectAltName "%s"' % san_list
|
||||||
|
raise exc.SSLCertificateError(msg)
|
||||||
|
|
||||||
|
|
||||||
def to_bytes(s):
|
def to_bytes(s):
|
||||||
if isinstance(s, six.string_types):
|
if isinstance(s, six.string_types):
|
||||||
return six.b(s)
|
return six.b(s)
|
||||||
@@ -197,61 +273,6 @@ class VerifiedHTTPSConnection(HTTPSConnection):
|
|||||||
except excp_lst as e:
|
except excp_lst as e:
|
||||||
raise exc.SSLConfigurationError(str(e))
|
raise exc.SSLConfigurationError(str(e))
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def host_matches_cert(host, x509):
|
|
||||||
"""
|
|
||||||
Verify that the x509 certificate we have received
|
|
||||||
from 'host' correctly identifies the server we are
|
|
||||||
connecting to, ie that the certificate's Common Name
|
|
||||||
or a Subject Alternative Name matches 'host'.
|
|
||||||
"""
|
|
||||||
def check_match(name):
|
|
||||||
# Directly match the name
|
|
||||||
if name == host:
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Support single wildcard matching
|
|
||||||
if name.startswith('*.') and host.find('.') > 0:
|
|
||||||
if name[2:] == host.split('.', 1)[1]:
|
|
||||||
return True
|
|
||||||
|
|
||||||
common_name = x509.get_subject().commonName
|
|
||||||
|
|
||||||
# First see if we can match the CN
|
|
||||||
if check_match(common_name):
|
|
||||||
return True
|
|
||||||
# Also try Subject Alternative Names for a match
|
|
||||||
san_list = None
|
|
||||||
for i in range(x509.get_extension_count()):
|
|
||||||
ext = x509.get_extension(i)
|
|
||||||
if ext.get_short_name() == b'subjectAltName':
|
|
||||||
san_list = str(ext)
|
|
||||||
for san in ''.join(san_list.split()).split(','):
|
|
||||||
if san.startswith('DNS:'):
|
|
||||||
if check_match(san.split(':', 1)[1]):
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Server certificate does not match host
|
|
||||||
msg = ('Host "%s" does not match x509 certificate contents: '
|
|
||||||
'CommonName "%s"' % (host, common_name))
|
|
||||||
if san_list is not None:
|
|
||||||
msg = msg + ', subjectAltName "%s"' % san_list
|
|
||||||
raise exc.SSLCertificateError(msg)
|
|
||||||
|
|
||||||
def verify_callback(self, connection, x509, errnum,
|
|
||||||
depth, preverify_ok):
|
|
||||||
if x509.has_expired():
|
|
||||||
msg = "SSL Certificate expired on '%s'" % x509.get_notAfter()
|
|
||||||
raise exc.SSLCertificateError(msg)
|
|
||||||
|
|
||||||
if depth == 0 and preverify_ok:
|
|
||||||
# We verify that the host matches against the last
|
|
||||||
# certificate in the chain
|
|
||||||
return self.host_matches_cert(self.host, x509)
|
|
||||||
else:
|
|
||||||
# Pass through OpenSSL's default result
|
|
||||||
return preverify_ok
|
|
||||||
|
|
||||||
def set_context(self):
|
def set_context(self):
|
||||||
"""
|
"""
|
||||||
Set up the OpenSSL context.
|
Set up the OpenSSL context.
|
||||||
@@ -264,7 +285,7 @@ class VerifiedHTTPSConnection(HTTPSConnection):
|
|||||||
|
|
||||||
if self.insecure is not True:
|
if self.insecure is not True:
|
||||||
self.context.set_verify(OpenSSL.SSL.VERIFY_PEER,
|
self.context.set_verify(OpenSSL.SSL.VERIFY_PEER,
|
||||||
self.verify_callback)
|
verify_callback(host=self.host))
|
||||||
else:
|
else:
|
||||||
self.context.set_verify(OpenSSL.SSL.VERIFY_NONE,
|
self.context.set_verify(OpenSSL.SSL.VERIFY_NONE,
|
||||||
lambda *args: True)
|
lambda *args: True)
|
||||||
|
@@ -158,7 +158,7 @@ class TestVerifiedHTTPSConnection(testtools.TestCase):
|
|||||||
self.assertEqual('0.0.0.0', cert.get_subject().commonName)
|
self.assertEqual('0.0.0.0', cert.get_subject().commonName)
|
||||||
try:
|
try:
|
||||||
conn = https.VerifiedHTTPSConnection('0.0.0.0', 0)
|
conn = https.VerifiedHTTPSConnection('0.0.0.0', 0)
|
||||||
conn.verify_callback(None, cert, 0, 0, 1)
|
https.do_verify_callback(None, cert, 0, 0, 1, host=conn.host)
|
||||||
except Exception:
|
except Exception:
|
||||||
self.fail('Unexpected exception.')
|
self.fail('Unexpected exception.')
|
||||||
|
|
||||||
@@ -173,7 +173,7 @@ class TestVerifiedHTTPSConnection(testtools.TestCase):
|
|||||||
self.assertEqual('*.pong.example.com', cert.get_subject().commonName)
|
self.assertEqual('*.pong.example.com', cert.get_subject().commonName)
|
||||||
try:
|
try:
|
||||||
conn = https.VerifiedHTTPSConnection('ping.pong.example.com', 0)
|
conn = https.VerifiedHTTPSConnection('ping.pong.example.com', 0)
|
||||||
conn.verify_callback(None, cert, 0, 0, 1)
|
https.do_verify_callback(None, cert, 0, 0, 1, host=conn.host)
|
||||||
except Exception:
|
except Exception:
|
||||||
self.fail('Unexpected exception.')
|
self.fail('Unexpected exception.')
|
||||||
|
|
||||||
@@ -188,13 +188,13 @@ class TestVerifiedHTTPSConnection(testtools.TestCase):
|
|||||||
self.assertEqual('0.0.0.0', cert.get_subject().commonName)
|
self.assertEqual('0.0.0.0', cert.get_subject().commonName)
|
||||||
try:
|
try:
|
||||||
conn = https.VerifiedHTTPSConnection('alt1.example.com', 0)
|
conn = https.VerifiedHTTPSConnection('alt1.example.com', 0)
|
||||||
conn.verify_callback(None, cert, 0, 0, 1)
|
https.do_verify_callback(None, cert, 0, 0, 1, host=conn.host)
|
||||||
except Exception:
|
except Exception:
|
||||||
self.fail('Unexpected exception.')
|
self.fail('Unexpected exception.')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
conn = https.VerifiedHTTPSConnection('alt2.example.com', 0)
|
conn = https.VerifiedHTTPSConnection('alt2.example.com', 0)
|
||||||
conn.verify_callback(None, cert, 0, 0, 1)
|
https.do_verify_callback(None, cert, 0, 0, 1, host=conn.host)
|
||||||
except Exception:
|
except Exception:
|
||||||
self.fail('Unexpected exception.')
|
self.fail('Unexpected exception.')
|
||||||
|
|
||||||
@@ -209,19 +209,19 @@ class TestVerifiedHTTPSConnection(testtools.TestCase):
|
|||||||
self.assertEqual('0.0.0.0', cert.get_subject().commonName)
|
self.assertEqual('0.0.0.0', cert.get_subject().commonName)
|
||||||
try:
|
try:
|
||||||
conn = https.VerifiedHTTPSConnection('alt1.example.com', 0)
|
conn = https.VerifiedHTTPSConnection('alt1.example.com', 0)
|
||||||
conn.verify_callback(None, cert, 0, 0, 1)
|
https.do_verify_callback(None, cert, 0, 0, 1, host=conn.host)
|
||||||
except Exception:
|
except Exception:
|
||||||
self.fail('Unexpected exception.')
|
self.fail('Unexpected exception.')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
conn = https.VerifiedHTTPSConnection('alt2.example.com', 0)
|
conn = https.VerifiedHTTPSConnection('alt2.example.com', 0)
|
||||||
conn.verify_callback(None, cert, 0, 0, 1)
|
https.do_verify_callback(None, cert, 0, 0, 1, host=conn.host)
|
||||||
except Exception:
|
except Exception:
|
||||||
self.fail('Unexpected exception.')
|
self.fail('Unexpected exception.')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
conn = https.VerifiedHTTPSConnection('alt3.example.net', 0)
|
conn = https.VerifiedHTTPSConnection('alt3.example.net', 0)
|
||||||
conn.verify_callback(None, cert, 0, 0, 1)
|
https.do_verify_callback(None, cert, 0, 0, 1, host=conn.host)
|
||||||
self.fail('Failed to raise assertion.')
|
self.fail('Failed to raise assertion.')
|
||||||
except exc.SSLCertificateError:
|
except exc.SSLCertificateError:
|
||||||
pass
|
pass
|
||||||
@@ -241,7 +241,8 @@ class TestVerifiedHTTPSConnection(testtools.TestCase):
|
|||||||
self.fail('Failed to init VerifiedHTTPSConnection.')
|
self.fail('Failed to init VerifiedHTTPSConnection.')
|
||||||
|
|
||||||
self.assertRaises(exc.SSLCertificateError,
|
self.assertRaises(exc.SSLCertificateError,
|
||||||
conn.verify_callback, None, cert, 0, 0, 1)
|
https.do_verify_callback, None, cert, 0, 0, 1,
|
||||||
|
host=conn.host)
|
||||||
|
|
||||||
def test_ssl_expired_cert(self):
|
def test_ssl_expired_cert(self):
|
||||||
"""
|
"""
|
||||||
@@ -256,9 +257,11 @@ class TestVerifiedHTTPSConnection(testtools.TestCase):
|
|||||||
try:
|
try:
|
||||||
conn = https.VerifiedHTTPSConnection('openstack.example.com', 0)
|
conn = https.VerifiedHTTPSConnection('openstack.example.com', 0)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
raise
|
||||||
self.fail('Failed to init VerifiedHTTPSConnection.')
|
self.fail('Failed to init VerifiedHTTPSConnection.')
|
||||||
self.assertRaises(exc.SSLCertificateError,
|
self.assertRaises(exc.SSLCertificateError,
|
||||||
conn.verify_callback, None, cert, 0, 0, 1)
|
https.do_verify_callback, None, cert, 0, 0, 1,
|
||||||
|
host=conn.host)
|
||||||
|
|
||||||
def test_ssl_broken_key_file(self):
|
def test_ssl_broken_key_file(self):
|
||||||
"""
|
"""
|
||||||
|
Reference in New Issue
Block a user