diff --git a/Lib/ssl.py b/Lib/ssl.py index dafb70a67864c4..903cb530db8ad3 100644 --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -779,16 +779,20 @@ def create_default_context(purpose=Purpose.SERVER_AUTH, *, cafile=None, context.keylog_filename = keylogfile return context -def _create_unverified_context(protocol=None, *, cert_reqs=CERT_NONE, - check_hostname=False, purpose=Purpose.SERVER_AUTH, - certfile=None, keyfile=None, - cafile=None, capath=None, cadata=None): - """Create a SSLContext object for Python stdlib modules + +def _create_verified_context( + protocol=None, *, cert_reqs=None, check_hostname=None, + purpose=Purpose.SERVER_AUTH, certfile=None, keyfile=None, + cafile=None, capath=None, cadata=None +): + """Create a SSLContext for Python stdlib modules (verified) 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. + + Cert and hostname verification is enabled by default for client contexts. """ if not isinstance(purpose, _ASN1Object): raise TypeError(purpose) @@ -800,9 +804,17 @@ def _create_unverified_context(protocol=None, *, cert_reqs=CERT_NONE, # verify certs and host name in client mode if protocol is None: protocol = PROTOCOL_TLS_CLIENT + if cert_reqs is None: + cert_reqs = CERT_REQUIRED + if check_hostname is None: + check_hostname = True elif purpose == Purpose.CLIENT_AUTH: if protocol is None: protocol = PROTOCOL_TLS_SERVER + if cert_reqs is None: + cert_reqs = CERT_NONE + if check_hostname is None: + check_hostname = False else: raise ValueError(purpose) @@ -833,12 +845,28 @@ def _create_unverified_context(protocol=None, *, cert_reqs=CERT_NONE, context.keylog_filename = keylogfile return context + +def _create_unverified_context( + protocol=None, *, cert_reqs=CERT_NONE, check_hostname=False, + purpose=Purpose.SERVER_AUTH, certfile=None, keyfile=None, + cafile=None, capath=None, cadata=None +): + """Create a SSLContext for Python stdlib modules (unverified) + + Cert and hostname verification is **disabled** by default. + """ + return _create_verified_context( + protocol, cert_reqs=cert_reqs, check_hostname=check_hostname, + purpose=purpose, certfile=certfile, keyfile=keyfile, + cafile=cafile, capath=capath, cadata=cadata + ) + + # Used by http.client if no context is explicitly passed. _create_default_https_context = create_default_context - # Backwards compatibility alias, even though it's not a public name. -_create_stdlib_context = _create_unverified_context +_create_stdlib_context = _create_verified_context class SSLObject: @@ -1520,9 +1548,9 @@ def get_server_certificate(addr, ssl_version=PROTOCOL_TLS_CLIENT, cert_reqs = CERT_REQUIRED else: cert_reqs = CERT_NONE - context = _create_stdlib_context(ssl_version, - cert_reqs=cert_reqs, - cafile=ca_certs) + context = _create_unverified_context( + ssl_version, cert_reqs=cert_reqs, cafile=ca_certs + ) with create_connection(addr, timeout=timeout) as sock: with context.wrap_socket(sock, server_hostname=host) as sslsock: dercert = sslsock.getpeercert(True) diff --git a/Lib/test/test_ftplib.py b/Lib/test/test_ftplib.py index 2794fccfe7c72d..6ae6cff7528488 100644 --- a/Lib/test/test_ftplib.py +++ b/Lib/test/test_ftplib.py @@ -914,6 +914,8 @@ def setUp(self, encoding=DEFAULT_ENCODING): self.server = DummyTLS_FTPServer((HOST, 0), encoding=encoding) self.server.start() self.client = ftplib.FTP_TLS(timeout=TIMEOUT, encoding=encoding) + self.client.context.check_hostname = False + self.client.context.verify_mode = ssl.CERT_NONE self.client.connect(self.server.host, self.server.port) # enable TLS self.client.auth() @@ -928,6 +930,8 @@ def setUp(self, encoding=DEFAULT_ENCODING): self.server = DummyTLS_FTPServer((HOST, 0), encoding=encoding) self.server.start() self.client = ftplib.FTP_TLS(timeout=TIMEOUT) + self.client.context.check_hostname = False + self.client.context.verify_mode = ssl.CERT_NONE self.client.connect(self.server.host, self.server.port) def tearDown(self): diff --git a/Lib/test/test_nntplib.py b/Lib/test/test_nntplib.py index 9812c055193517..b3b001a0b23b47 100644 --- a/Lib/test/test_nntplib.py +++ b/Lib/test/test_nntplib.py @@ -1632,7 +1632,8 @@ def run_server(self, sock): def test_starttls(self): file = self.nntp.file sock = self.nntp.sock - self.nntp.starttls() + context = ssl._create_unverified_context() + self.nntp.starttls(context=context) # Check that the socket and internal pseudo-file really were # changed. self.assertNotEqual(file, self.nntp.file) diff --git a/Lib/test/test_poplib.py b/Lib/test/test_poplib.py index 57ccc541b81fe1..731fa8be26ced0 100644 --- a/Lib/test/test_poplib.py +++ b/Lib/test/test_poplib.py @@ -17,6 +17,7 @@ from test.support import threading_helper import warnings + with warnings.catch_warnings(): warnings.simplefilter('ignore', DeprecationWarning) import asynchat @@ -417,6 +418,7 @@ def setUp(self): self.server.handler = DummyPOP3_SSLHandler self.server.start() self.client = poplib.POP3_SSL(self.server.host, self.server.port) + self.client.context.load_verify_locations(CAFILE) def test__all__(self): self.assertIn('POP3_SSL', poplib.__all__) @@ -459,6 +461,7 @@ def setUp(self): self.server.start() self.client = poplib.POP3(self.server.host, self.server.port, timeout=test_support.LOOPBACK_TIMEOUT) + self.client.context.load_verify_locations(CAFILE) self.client.stls() def tearDown(self): diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index 543d34a5469333..62975f0234e8a6 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -1709,23 +1709,54 @@ def test_create_default_context(self): self.assertEqual(ctx.verify_mode, ssl.CERT_NONE) self._assert_context_options(ctx) + def test__create_stdlib_context(self): + self.assertIs(ssl._create_stdlib_context, ssl._create_verified_context) + def test__create_verified_context(self): + ctx = ssl._create_verified_context() + self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLS_CLIENT) + self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED) + self.assertTrue(ctx.check_hostname) + self._assert_context_options(ctx) - def test__create_stdlib_context(self): - ctx = ssl._create_stdlib_context() + with warnings_helper.check_warnings(): + ctx = ssl._create_verified_context(ssl.PROTOCOL_TLSv1) + self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLSv1) + self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED) + self.assertTrue(ctx.check_hostname) + self._assert_context_options(ctx) + + with warnings_helper.check_warnings(): + ctx = ssl._create_verified_context( + ssl.PROTOCOL_TLSv1_2, + cert_reqs=ssl.CERT_NONE, + check_hostname=False + ) + self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLSv1_2) + self.assertEqual(ctx.verify_mode, ssl.CERT_NONE) + self.assertFalse(ctx.check_hostname) + self._assert_context_options(ctx) + + ctx = ssl._create_verified_context(purpose=ssl.Purpose.CLIENT_AUTH) + self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLS_SERVER) + self.assertEqual(ctx.verify_mode, ssl.CERT_NONE) + self._assert_context_options(ctx) + + def test__create_unverified_context(self): + ctx = ssl._create_unverified_context() self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLS_CLIENT) self.assertEqual(ctx.verify_mode, ssl.CERT_NONE) self.assertFalse(ctx.check_hostname) self._assert_context_options(ctx) with warnings_helper.check_warnings(): - ctx = ssl._create_stdlib_context(ssl.PROTOCOL_TLSv1) + ctx = ssl._create_unverified_context(ssl.PROTOCOL_TLSv1) self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLSv1) self.assertEqual(ctx.verify_mode, ssl.CERT_NONE) self._assert_context_options(ctx) with warnings_helper.check_warnings(): - ctx = ssl._create_stdlib_context( + ctx = ssl._create_unverified_context( ssl.PROTOCOL_TLSv1_2, cert_reqs=ssl.CERT_REQUIRED, check_hostname=True @@ -1735,7 +1766,7 @@ def test__create_stdlib_context(self): self.assertTrue(ctx.check_hostname) self._assert_context_options(ctx) - ctx = ssl._create_stdlib_context(purpose=ssl.Purpose.CLIENT_AUTH) + ctx = ssl._create_unverified_context(purpose=ssl.Purpose.CLIENT_AUTH) self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLS_SERVER) self.assertEqual(ctx.verify_mode, ssl.CERT_NONE) self._assert_context_options(ctx) diff --git a/Misc/NEWS.d/next/Security/2022-04-24-11-34-31.gh-issue-91826.bfumF-.rst b/Misc/NEWS.d/next/Security/2022-04-24-11-34-31.gh-issue-91826.bfumF-.rst new file mode 100644 index 00000000000000..2544d0a5c82e15 --- /dev/null +++ b/Misc/NEWS.d/next/Security/2022-04-24-11-34-31.gh-issue-91826.bfumF-.rst @@ -0,0 +1,2 @@ +:mod:`ftplib`, :mod:`imaplib`, :mod:`nntplib`, :mod:`poplib`, and +:mod:`smtplib` now verify TLS certificate and hostname by default.