Issue #19735: Implement private function ssl._create_stdlib_context() to

create SSLContext objects in Python's stdlib module. It provides a single
configuration point and makes use of SSLContext.load_default_certs().
This commit is contained in:
Christian Heimes 2013-11-23 22:43:47 +01:00
parent 32eddc1bbc
commit 67986f9431
11 changed files with 100 additions and 55 deletions

View File

@ -571,10 +571,8 @@ class _SelectorSslTransport(_SelectorTransport):
# context; in that case the sslcontext passed is None. # context; in that case the sslcontext passed is None.
# The default is the same as used by urllib with # The default is the same as used by urllib with
# cadefault=True. # cadefault=True.
sslcontext = ssl.SSLContext(ssl.PROTOCOL_SSLv23) sslcontext = ssl._create_stdlib_context(
sslcontext.options |= ssl.OP_NO_SSLv2 cert_reqs=ssl.CERT_REQUIRED)
sslcontext.set_default_verify_paths()
sslcontext.verify_mode = ssl.CERT_REQUIRED
wrap_kwargs = { wrap_kwargs = {
'server_side': server_side, 'server_side': server_side,

View File

@ -727,6 +727,10 @@ else:
"exclusive") "exclusive")
self.keyfile = keyfile self.keyfile = keyfile
self.certfile = certfile self.certfile = certfile
if context is None:
context = ssl._create_stdlib_context(self.ssl_version,
certfile=certfile,
keyfile=keyfile)
self.context = context self.context = context
self._prot_p = False self._prot_p = False
FTP.__init__(self, host, user, passwd, acct, timeout, source_address) FTP.__init__(self, host, user, passwd, acct, timeout, source_address)
@ -744,12 +748,7 @@ else:
resp = self.voidcmd('AUTH TLS') resp = self.voidcmd('AUTH TLS')
else: else:
resp = self.voidcmd('AUTH SSL') resp = self.voidcmd('AUTH SSL')
if self.context is not None: self.sock = self.context.wrap_socket(self.sock)
self.sock = self.context.wrap_socket(self.sock)
else:
self.sock = ssl.wrap_socket(self.sock, self.keyfile,
self.certfile,
ssl_version=self.ssl_version)
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,11 +787,7 @@ 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:
if self.context is not None: conn = self.context.wrap_socket(conn)
conn = self.context.wrap_socket(conn)
else:
conn = ssl.wrap_socket(conn, self.keyfile, self.certfile,
ssl_version=self.ssl_version)
return conn, size return conn, size
def abort(self): def abort(self):

View File

@ -1179,9 +1179,7 @@ else:
self.key_file = key_file self.key_file = key_file
self.cert_file = cert_file self.cert_file = cert_file
if context is None: if context is None:
# Some reasonable defaults context = ssl._create_stdlib_context()
context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
context.options |= ssl.OP_NO_SSLv2
will_verify = context.verify_mode != ssl.CERT_NONE will_verify = context.verify_mode != ssl.CERT_NONE
if check_hostname is None: if check_hostname is None:
check_hostname = will_verify check_hostname = will_verify

View File

@ -742,9 +742,7 @@ class IMAP4:
raise self.abort('TLS not supported by server') raise self.abort('TLS not supported by server')
# Generate a default SSL context if none was passed. # Generate a default SSL context if none was passed.
if ssl_context is None: if ssl_context is None:
ssl_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) ssl_context = ssl._create_stdlib_context()
# SSLv2 considered harmful.
ssl_context.options |= ssl.OP_NO_SSLv2
typ, dat = self._simple_command(name) typ, dat = self._simple_command(name)
if typ == 'OK': if typ == 'OK':
self.sock = ssl_context.wrap_socket(self.sock) self.sock = ssl_context.wrap_socket(self.sock)
@ -1210,15 +1208,15 @@ if HAVE_SSL:
self.keyfile = keyfile self.keyfile = keyfile
self.certfile = certfile self.certfile = certfile
if ssl_context is None:
ssl_context = ssl._create_stdlib_context(certfile=certfile,
keyfile=keyfile)
self.ssl_context = ssl_context self.ssl_context = ssl_context
IMAP4.__init__(self, host, port) IMAP4.__init__(self, host, port)
def _create_socket(self): def _create_socket(self):
sock = IMAP4._create_socket(self) sock = IMAP4._create_socket(self)
if self.ssl_context: return self.ssl_context.wrap_socket(sock)
return self.ssl_context.wrap_socket(sock)
else:
return ssl.wrap_socket(sock, self.keyfile, self.certfile)
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

@ -288,9 +288,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.SSLContext(ssl.PROTOCOL_SSLv23) context = ssl._create_stdlib_context()
# SSLv2 considered harmful.
context.options |= ssl.OP_NO_SSLv2
return context.wrap_socket(sock) return context.wrap_socket(sock)

View File

@ -385,8 +385,7 @@ class POP3:
if not 'STLS' in caps: if not 'STLS' in caps:
raise error_proto('-ERR STLS not supported by server') raise error_proto('-ERR STLS not supported by server')
if context is None: if context is None:
context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) context = ssl._create_stdlib_context()
context.options |= ssl.OP_NO_SSLv2
resp = self._shortcmd('STLS') resp = self._shortcmd('STLS')
self.sock = context.wrap_socket(self.sock) self.sock = context.wrap_socket(self.sock)
self.file = self.sock.makefile('rb') self.file = self.sock.makefile('rb')
@ -421,15 +420,15 @@ if HAVE_SSL:
"exclusive") "exclusive")
self.keyfile = keyfile self.keyfile = keyfile
self.certfile = certfile self.certfile = certfile
if context is None:
context = ssl._create_stdlib_context(certfile=certfile,
keyfile=keyfile)
self.context = context self.context = context
POP3.__init__(self, host, port, timeout) POP3.__init__(self, host, port, timeout)
def _create_socket(self, timeout): def _create_socket(self, timeout):
sock = POP3._create_socket(self, timeout) sock = POP3._create_socket(self, timeout)
if self.context is not None: sock = self.context.wrap_socket(sock)
sock = self.context.wrap_socket(sock)
else:
sock = ssl.wrap_socket(sock, self.keyfile, self.certfile)
return sock return sock
def stls(self, keyfile=None, certfile=None, context=None): def stls(self, keyfile=None, certfile=None, context=None):

View File

@ -664,10 +664,10 @@ class SMTP:
if context is not None and certfile is not None: if context is not None and certfile is not None:
raise ValueError("context and certfile arguments are mutually " raise ValueError("context and certfile arguments are mutually "
"exclusive") "exclusive")
if context is not None: if context is None:
self.sock = context.wrap_socket(self.sock) context = ssl._create_stdlib_context(certfile=certfile,
else: keyfile=keyfile)
self.sock = ssl.wrap_socket(self.sock, keyfile, certfile) self.sock = context.wrap_socket(self.sock)
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
@ -880,6 +880,9 @@ if _have_ssl:
"exclusive") "exclusive")
self.keyfile = keyfile self.keyfile = keyfile
self.certfile = certfile self.certfile = certfile
if context is None:
context = ssl._create_stdlib_context(certfile=certfile,
keyfile=keyfile)
self.context = context self.context = context
SMTP.__init__(self, host, port, local_hostname, timeout, SMTP.__init__(self, host, port, local_hostname, timeout,
source_address) source_address)
@ -889,10 +892,7 @@ 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)
if self.context is not None: new_socket = self.context.wrap_socket(new_socket)
new_socket = self.context.wrap_socket(new_socket)
else:
new_socket = ssl.wrap_socket(new_socket, self.keyfile, self.certfile)
return new_socket return new_socket
__all__.append("SMTP_SSL") __all__.append("SMTP_SSL")

View File

@ -398,6 +398,43 @@ def create_default_context(purpose=Purpose.SERVER_AUTH, *, cafile=None,
return context return context
def _create_stdlib_context(protocol=PROTOCOL_SSLv23, *, cert_reqs=None,
purpose=Purpose.SERVER_AUTH,
certfile=None, keyfile=None,
cafile=None, capath=None, cadata=None):
"""Create a SSLContext object for Python stdlib modules
All Python stdlib modules shall use this function to create SSLContext
objects in order to keep common settings in one place. The configuration
is less restrict than create_default_context()'s to increase backward
compatibility.
"""
if not isinstance(purpose, _ASN1Object):
raise TypeError(purpose)
context = SSLContext(protocol)
# SSLv2 considered harmful.
context.options |= OP_NO_SSLv2
if cert_reqs is not None:
context.verify_mode = cert_reqs
if keyfile and not certfile:
raise ValueError("certfile must be specified")
if certfile or keyfile:
context.load_cert_chain(certfile, keyfile)
# load CA root certs
if cafile or capath or cadata:
context.load_verify_locations(cafile, capath, cadata)
elif context.verify_mode != CERT_NONE:
# no explicit cafile, capath or cadata but the verify mode is
# CERT_OPTIONAL or CERT_REQUIRED. Let's try to load default system
# root CA certificates for the given purpose. This may fail silently.
context.load_default_certs(purpose)
return context
class SSLSocket(socket): class SSLSocket(socket):
"""This class implements a subtype of socket.socket that wraps """This class implements a subtype of socket.socket that wraps
the underlying OS socket in an SSL context when necessary, and the underlying OS socket in an SSL context when necessary, and
@ -829,15 +866,16 @@ def get_server_certificate(addr, ssl_version=PROTOCOL_SSLv3, ca_certs=None):
If 'ssl_version' is specified, use it in the connection attempt.""" If 'ssl_version' is specified, use it in the connection attempt."""
host, port = addr host, port = addr
if (ca_certs is not None): if ca_certs is not None:
cert_reqs = CERT_REQUIRED cert_reqs = CERT_REQUIRED
else: else:
cert_reqs = CERT_NONE cert_reqs = CERT_NONE
s = create_connection(addr) context = _create_stdlib_context(ssl_version,
s = wrap_socket(s, ssl_version=ssl_version, cert_reqs=cert_reqs,
cert_reqs=cert_reqs, ca_certs=ca_certs) cafile=ca_certs)
dercert = s.getpeercert(True) with create_connection(addr) as sock:
s.close() with context.wrap_socket(sock) as sslsock:
dercert = sslsock.getpeercert(True)
return DER_cert_to_PEM_cert(dercert) return DER_cert_to_PEM_cert(dercert)
def get_protocol_name(protocol_code): def get_protocol_name(protocol_code):

View File

@ -1018,6 +1018,27 @@ class ContextTests(unittest.TestCase):
self.assertEqual(ctx.verify_mode, ssl.CERT_NONE) self.assertEqual(ctx.verify_mode, ssl.CERT_NONE)
self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2) self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2)
def test__create_stdlib_context(self):
ctx = ssl._create_stdlib_context()
self.assertEqual(ctx.protocol, ssl.PROTOCOL_SSLv23)
self.assertEqual(ctx.verify_mode, ssl.CERT_NONE)
self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2)
ctx = ssl._create_stdlib_context(ssl.PROTOCOL_TLSv1)
self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLSv1)
self.assertEqual(ctx.verify_mode, ssl.CERT_NONE)
self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2)
ctx = ssl._create_stdlib_context(ssl.PROTOCOL_TLSv1,
cert_reqs=ssl.CERT_REQUIRED)
self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLSv1)
self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED)
self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2)
ctx = ssl._create_stdlib_context(purpose=ssl.Purpose.CLIENT_AUTH)
self.assertEqual(ctx.protocol, ssl.PROTOCOL_SSLv23)
self.assertEqual(ctx.verify_mode, ssl.CERT_NONE)
self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2)
class SSLErrorTests(unittest.TestCase): class SSLErrorTests(unittest.TestCase):

View File

@ -141,13 +141,9 @@ def urlopen(url, data=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
if cafile or capath or cadefault: if cafile or capath or cadefault:
if not _have_ssl: if not _have_ssl:
raise ValueError('SSL support not available') raise ValueError('SSL support not available')
context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) context = ssl._create_stdlib_context(cert_reqs=ssl.CERT_REQUIRED,
context.options |= ssl.OP_NO_SSLv2 cafile=cafile,
context.verify_mode = ssl.CERT_REQUIRED capath=capath)
if cafile or capath:
context.load_verify_locations(cafile, capath)
else:
context.set_default_verify_paths()
https_handler = HTTPSHandler(context=context, check_hostname=True) https_handler = HTTPSHandler(context=context, check_hostname=True)
opener = build_opener(https_handler) opener = build_opener(https_handler)
elif _opener is None: elif _opener is None:

View File

@ -68,6 +68,10 @@ Core and Builtins
Library Library
------- -------
- Issue #19735: Implement private function ssl._create_stdlib_context() to
create SSLContext objects in Python's stdlib module. It provides a single
configuration point and makes use of SSLContext.load_default_certs().
- Issue #16203: Add re.fullmatch() function and regex.fullmatch() method, - Issue #16203: Add re.fullmatch() function and regex.fullmatch() method,
which anchor the pattern at both ends of the string to match. which anchor the pattern at both ends of the string to match.
Original patch by Matthew Barnett. Original patch by Matthew Barnett.