don't require OpenSSL SNI to pass hostname to ssl functions (#22921)

Patch by Donald Stufft.
This commit is contained in:
Benjamin Peterson 2014-11-23 17:04:34 -06:00
parent 378e15d7ab
commit 7243b574e5
16 changed files with 22 additions and 67 deletions

View File

@ -664,8 +664,7 @@ Constants
.. data:: HAS_SNI .. data:: HAS_SNI
Whether the OpenSSL library has built-in support for the *Server Name Whether the OpenSSL library has built-in support for the *Server Name
Indication* extension (as defined in :rfc:`4366`). When true, you can Indication* extension (as defined in :rfc:`4366`).
use the *server_hostname* argument to :meth:`SSLContext.wrap_socket`.
.. versionadded:: 3.2 .. versionadded:: 3.2
@ -1227,11 +1226,12 @@ to speed up repeated connections from the same clients.
On client connections, the optional parameter *server_hostname* specifies On client connections, the optional parameter *server_hostname* specifies
the hostname of the service which we are connecting to. This allows a the hostname of the service which we are connecting to. This allows a
single server to host multiple SSL-based services with distinct certificates, single server to host multiple SSL-based services with distinct certificates,
quite similarly to HTTP virtual hosts. Specifying *server_hostname* quite similarly to HTTP virtual hosts. Specifying *server_hostname* will
will raise a :exc:`ValueError` if the OpenSSL library doesn't have support raise a :exc:`ValueError` if *server_side* is true.
for it (that is, if :data:`HAS_SNI` is :const:`False`). Specifying
*server_hostname* will also raise a :exc:`ValueError` if *server_side* .. versionchanged:: 3.5
is true. Always allow a server_hostname to be passed, even if OpenSSL does not
have SNI.
.. method:: SSLContext.session_stats() .. method:: SSLContext.session_stats()

View File

@ -708,7 +708,7 @@ class _SelectorSslTransport(_SelectorTransport):
'server_side': server_side, 'server_side': server_side,
'do_handshake_on_connect': False, 'do_handshake_on_connect': False,
} }
if server_hostname and not server_side and ssl.HAS_SNI: if server_hostname and not server_side:
wrap_kwargs['server_hostname'] = server_hostname wrap_kwargs['server_hostname'] = server_hostname
sslsock = sslcontext.wrap_socket(rawsock, **wrap_kwargs) sslsock = sslcontext.wrap_socket(rawsock, **wrap_kwargs)

View File

@ -747,9 +747,8 @@ else:
resp = self.voidcmd('AUTH TLS') resp = self.voidcmd('AUTH TLS')
else: else:
resp = self.voidcmd('AUTH SSL') resp = self.voidcmd('AUTH SSL')
server_hostname = self.host if ssl.HAS_SNI else None
self.sock = self.context.wrap_socket(self.sock, self.sock = self.context.wrap_socket(self.sock,
server_hostname=server_hostname) server_hostname=self.host)
self.file = self.sock.makefile(mode='r', encoding=self.encoding) self.file = self.sock.makefile(mode='r', encoding=self.encoding)
return resp return resp
@ -788,9 +787,8 @@ else:
def ntransfercmd(self, cmd, rest=None): def ntransfercmd(self, cmd, rest=None):
conn, size = FTP.ntransfercmd(self, cmd, rest) conn, size = FTP.ntransfercmd(self, cmd, rest)
if self._prot_p: if self._prot_p:
server_hostname = self.host if ssl.HAS_SNI else None
conn = self.context.wrap_socket(conn, conn = self.context.wrap_socket(conn,
server_hostname=server_hostname) server_hostname=self.host)
return conn, size return conn, size
def abort(self): def abort(self):

View File

@ -1224,10 +1224,9 @@ else:
server_hostname = self._tunnel_host server_hostname = self._tunnel_host
else: else:
server_hostname = self.host server_hostname = self.host
sni_hostname = server_hostname if ssl.HAS_SNI else None
self.sock = self._context.wrap_socket(self.sock, self.sock = self._context.wrap_socket(self.sock,
server_hostname=sni_hostname) server_hostname=server_hostname)
if not self._context.check_hostname and self._check_hostname: if not self._context.check_hostname and self._check_hostname:
try: try:
ssl.match_hostname(self.sock.getpeercert(), server_hostname) ssl.match_hostname(self.sock.getpeercert(), server_hostname)

View File

@ -745,9 +745,8 @@ class IMAP4:
ssl_context = ssl._create_stdlib_context() ssl_context = ssl._create_stdlib_context()
typ, dat = self._simple_command(name) typ, dat = self._simple_command(name)
if typ == 'OK': if typ == 'OK':
server_hostname = self.host if ssl.HAS_SNI else None
self.sock = ssl_context.wrap_socket(self.sock, self.sock = ssl_context.wrap_socket(self.sock,
server_hostname=server_hostname) server_hostname=self.host)
self.file = self.sock.makefile('rb') self.file = self.sock.makefile('rb')
self._tls_established = True self._tls_established = True
self._get_capabilities() self._get_capabilities()
@ -1223,9 +1222,8 @@ if HAVE_SSL:
def _create_socket(self): def _create_socket(self):
sock = IMAP4._create_socket(self) sock = IMAP4._create_socket(self)
server_hostname = self.host if ssl.HAS_SNI else None
return self.ssl_context.wrap_socket(sock, return self.ssl_context.wrap_socket(sock,
server_hostname=server_hostname) server_hostname=self.host)
def open(self, host='', port=IMAP4_SSL_PORT): def open(self, host='', port=IMAP4_SSL_PORT):
"""Setup connection to remote server on "host:port". """Setup connection to remote server on "host:port".

View File

@ -289,8 +289,7 @@ if _have_ssl:
# Generate a default SSL context if none was passed. # Generate a default SSL context if none was passed.
if context is None: if context is None:
context = ssl._create_stdlib_context() context = ssl._create_stdlib_context()
server_hostname = hostname if ssl.HAS_SNI else None return context.wrap_socket(sock, server_hostname=hostname)
return context.wrap_socket(sock, server_hostname=server_hostname)
# The classes themselves # The classes themselves

View File

@ -387,9 +387,8 @@ class POP3:
if context is None: if context is None:
context = ssl._create_stdlib_context() context = ssl._create_stdlib_context()
resp = self._shortcmd('STLS') resp = self._shortcmd('STLS')
server_hostname = self.host if ssl.HAS_SNI else None
self.sock = context.wrap_socket(self.sock, self.sock = context.wrap_socket(self.sock,
server_hostname=server_hostname) server_hostname=self.host)
self.file = self.sock.makefile('rb') self.file = self.sock.makefile('rb')
self._tls_established = True self._tls_established = True
return resp return resp
@ -430,9 +429,8 @@ if HAVE_SSL:
def _create_socket(self, timeout): def _create_socket(self, timeout):
sock = POP3._create_socket(self, timeout) sock = POP3._create_socket(self, timeout)
server_hostname = self.host if ssl.HAS_SNI else None
sock = self.context.wrap_socket(sock, sock = self.context.wrap_socket(sock,
server_hostname=server_hostname) server_hostname=self.host)
return sock return sock
def stls(self, keyfile=None, certfile=None, context=None): def stls(self, keyfile=None, certfile=None, context=None):

View File

@ -684,9 +684,8 @@ class SMTP:
if context is None: if context is None:
context = ssl._create_stdlib_context(certfile=certfile, context = ssl._create_stdlib_context(certfile=certfile,
keyfile=keyfile) keyfile=keyfile)
server_hostname = self._host if ssl.HAS_SNI else None
self.sock = context.wrap_socket(self.sock, self.sock = context.wrap_socket(self.sock,
server_hostname=server_hostname) server_hostname=self._host)
self.file = None self.file = None
# RFC 3207: # RFC 3207:
# The client MUST discard any knowledge obtained from # The client MUST discard any knowledge obtained from
@ -915,9 +914,8 @@ if _have_ssl:
print('connect:', (host, port), file=stderr) print('connect:', (host, port), file=stderr)
new_socket = socket.create_connection((host, port), timeout, new_socket = socket.create_connection((host, port), timeout,
self.source_address) self.source_address)
server_hostname = self._host if ssl.HAS_SNI else None
new_socket = self.context.wrap_socket(new_socket, new_socket = self.context.wrap_socket(new_socket,
server_hostname=server_hostname) server_hostname=self._host)
return new_socket return new_socket
__all__.append("SMTP_SSL") __all__.append("SMTP_SSL")

View File

@ -538,12 +538,7 @@ class SSLSocket(socket):
raise ValueError("server_hostname can only be specified " raise ValueError("server_hostname can only be specified "
"in client mode") "in client mode")
if self._context.check_hostname and not server_hostname: if self._context.check_hostname and not server_hostname:
if HAS_SNI: raise ValueError("check_hostname requires server_hostname")
raise ValueError("check_hostname requires server_hostname")
else:
raise ValueError("check_hostname requires server_hostname, "
"but it's not supported by your OpenSSL "
"library")
self.server_side = server_side self.server_side = server_side
self.server_hostname = server_hostname self.server_hostname = server_hostname
self.do_handshake_on_connect = do_handshake_on_connect self.do_handshake_on_connect = do_handshake_on_connect

View File

@ -12,9 +12,6 @@ try:
import ssl import ssl
except ImportError: except ImportError:
ssl = None ssl = None
HAS_SNI = False
else:
from ssl import HAS_SNI
import subprocess import subprocess
import sys import sys
import threading import threading
@ -857,7 +854,6 @@ class EventLoopTestsMixin:
server.close() server.close()
@unittest.skipIf(ssl is None, 'No ssl module') @unittest.skipIf(ssl is None, 'No ssl module')
@unittest.skipUnless(HAS_SNI, 'No SNI support in ssl module')
def test_create_server_ssl_verify_failed(self): def test_create_server_ssl_verify_failed(self):
proto = MyProto(loop=self.loop) proto = MyProto(loop=self.loop)
server, host, port = self._make_ssl_server( server, host, port = self._make_ssl_server(
@ -882,7 +878,6 @@ class EventLoopTestsMixin:
server.close() server.close()
@unittest.skipIf(ssl is None, 'No ssl module') @unittest.skipIf(ssl is None, 'No ssl module')
@unittest.skipUnless(HAS_SNI, 'No SNI support in ssl module')
@unittest.skipUnless(hasattr(socket, 'AF_UNIX'), 'No UNIX Sockets') @unittest.skipUnless(hasattr(socket, 'AF_UNIX'), 'No UNIX Sockets')
def test_create_unix_server_ssl_verify_failed(self): def test_create_unix_server_ssl_verify_failed(self):
proto = MyProto(loop=self.loop) proto = MyProto(loop=self.loop)
@ -909,7 +904,6 @@ class EventLoopTestsMixin:
server.close() server.close()
@unittest.skipIf(ssl is None, 'No ssl module') @unittest.skipIf(ssl is None, 'No ssl module')
@unittest.skipUnless(HAS_SNI, 'No SNI support in ssl module')
def test_create_server_ssl_match_failed(self): def test_create_server_ssl_match_failed(self):
proto = MyProto(loop=self.loop) proto = MyProto(loop=self.loop)
server, host, port = self._make_ssl_server( server, host, port = self._make_ssl_server(
@ -937,7 +931,6 @@ class EventLoopTestsMixin:
server.close() server.close()
@unittest.skipIf(ssl is None, 'No ssl module') @unittest.skipIf(ssl is None, 'No ssl module')
@unittest.skipUnless(HAS_SNI, 'No SNI support in ssl module')
@unittest.skipUnless(hasattr(socket, 'AF_UNIX'), 'No UNIX Sockets') @unittest.skipUnless(hasattr(socket, 'AF_UNIX'), 'No UNIX Sockets')
def test_create_unix_server_ssl_verified(self): def test_create_unix_server_ssl_verified(self):
proto = MyProto(loop=self.loop) proto = MyProto(loop=self.loop)
@ -963,7 +956,6 @@ class EventLoopTestsMixin:
server.close() server.close()
@unittest.skipIf(ssl is None, 'No ssl module') @unittest.skipIf(ssl is None, 'No ssl module')
@unittest.skipUnless(HAS_SNI, 'No SNI support in ssl module')
def test_create_server_ssl_verified(self): def test_create_server_ssl_verified(self):
proto = MyProto(loop=self.loop) proto = MyProto(loop=self.loop)
server, host, port = self._make_ssl_server( server, host, port = self._make_ssl_server(

View File

@ -1408,7 +1408,7 @@ class SelectorSslTransportTests(test_utils.TestCase):
self.assertEqual(tr._conn_lost, 1) self.assertEqual(tr._conn_lost, 1)
self.assertEqual(1, self.loop.remove_reader_count[1]) self.assertEqual(1, self.loop.remove_reader_count[1])
@unittest.skipIf(ssl is None or not ssl.HAS_SNI, 'No SNI support') @unittest.skipIf(ssl is None, 'No SSL support')
def test_server_hostname(self): def test_server_hostname(self):
_SelectorSslTransport( _SelectorSslTransport(
self.loop, self.sock, self.protocol, self.sslcontext, self.loop, self.sock, self.protocol, self.sslcontext,

View File

@ -15,9 +15,6 @@ try:
import ssl import ssl
except ImportError: except ImportError:
ssl = None ssl = None
HAS_SNI = False
else:
from ssl import HAS_SNI
from unittest import TestCase, skipUnless from unittest import TestCase, skipUnless
from test import support from test import support
@ -927,7 +924,6 @@ class TestTLS_FTPClass(TestCase):
self.client.ccc() self.client.ccc()
self.assertRaises(ValueError, self.client.sock.unwrap) self.assertRaises(ValueError, self.client.sock.unwrap)
@skipUnless(HAS_SNI, 'No SNI support in ssl module')
def test_check_hostname(self): def test_check_hostname(self):
self.client.quit() self.client.quit()
ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1) ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)

View File

@ -18,9 +18,6 @@ try:
import ssl import ssl
except ImportError: except ImportError:
ssl = None ssl = None
HAS_SNI = False
else:
from ssl import HAS_SNI
CERTFILE = None CERTFILE = None
CAFILE = None CAFILE = None
@ -352,7 +349,6 @@ class ThreadedNetworkedTestsSSL(BaseThreadedNetworkedTests):
imap_class = IMAP4_SSL imap_class = IMAP4_SSL
@reap_threads @reap_threads
@unittest.skipUnless(HAS_SNI, 'No SNI support in ssl module')
def test_ssl_verified(self): def test_ssl_verified(self):
ssl_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) ssl_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
ssl_context.verify_mode = ssl.CERT_REQUIRED ssl_context.verify_mode = ssl.CERT_REQUIRED

View File

@ -21,13 +21,10 @@ PORT = 0
SUPPORTS_SSL = False SUPPORTS_SSL = False
if hasattr(poplib, 'POP3_SSL'): if hasattr(poplib, 'POP3_SSL'):
import ssl import ssl
from ssl import HAS_SNI
SUPPORTS_SSL = True SUPPORTS_SSL = True
CERTFILE = os.path.join(os.path.dirname(__file__) or os.curdir, "keycert3.pem") CERTFILE = os.path.join(os.path.dirname(__file__) or os.curdir, "keycert3.pem")
CAFILE = os.path.join(os.path.dirname(__file__) or os.curdir, "pycacert.pem") CAFILE = os.path.join(os.path.dirname(__file__) or os.curdir, "pycacert.pem")
else:
HAS_SNI = False
requires_ssl = skipUnless(SUPPORTS_SSL, 'SSL not supported') requires_ssl = skipUnless(SUPPORTS_SSL, 'SSL not supported')
@ -334,7 +331,6 @@ class TestPOP3Class(TestCase):
self.assertEqual(resp, expected) self.assertEqual(resp, expected)
@requires_ssl @requires_ssl
@skipUnless(HAS_SNI, 'No SNI support in ssl module')
def test_stls_context(self): def test_stls_context(self):
expected = b'+OK Begin TLS negotiation' expected = b'+OK Begin TLS negotiation'
ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1) ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)

View File

@ -1281,11 +1281,8 @@ class NetworkedTests(unittest.TestCase):
# Same with a server hostname # Same with a server hostname
s = ctx.wrap_socket(socket.socket(socket.AF_INET), s = ctx.wrap_socket(socket.socket(socket.AF_INET),
server_hostname="svn.python.org") server_hostname="svn.python.org")
if ssl.HAS_SNI: s.connect(("svn.python.org", 443))
s.connect(("svn.python.org", 443)) s.close()
s.close()
else:
self.assertRaises(ValueError, s.connect, ("svn.python.org", 443))
# This should fail because we have no verification certs # This should fail because we have no verification certs
ctx.verify_mode = ssl.CERT_REQUIRED ctx.verify_mode = ssl.CERT_REQUIRED
s = ctx.wrap_socket(socket.socket(socket.AF_INET)) s = ctx.wrap_socket(socket.socket(socket.AF_INET))
@ -2038,7 +2035,6 @@ else:
cert = s.getpeercert() cert = s.getpeercert()
self.assertTrue(cert, "Can't get peer certificate.") self.assertTrue(cert, "Can't get peer certificate.")
@needs_sni
def test_check_hostname(self): def test_check_hostname(self):
if support.verbose: if support.verbose:
sys.stdout.write("\n") sys.stdout.write("\n")

View File

@ -2806,12 +2806,6 @@ context_wrap_socket(PySSLContext *self, PyObject *args, PyObject *kwds)
&sock, &server_side, &sock, &server_side,
"idna", &hostname)) "idna", &hostname))
return NULL; return NULL;
#if !HAVE_SNI
PyMem_Free(hostname);
PyErr_SetString(PyExc_ValueError, "server_hostname is not supported "
"by your OpenSSL library");
return NULL;
#endif
} }
res = (PyObject *) newPySSLSocket(self, sock, server_side, res = (PyObject *) newPySSLSocket(self, sock, server_side,