diff -r c0224ff67cdd Doc/library/http.client.rst --- a/Doc/library/http.client.rst Mon Oct 13 10:39:41 2014 +0100 +++ b/Doc/library/http.client.rst Mon Oct 13 10:26:38 2014 -0700 @@ -71,12 +71,6 @@ :func:`ssl.create_default_context` select the system's trusted CA certificates for you. - The recommended way to connect to HTTPS hosts on the Internet is as - follows:: - - context = ssl.create_default_context() - h = client.HTTPSConnection('www.python.org', 443, context=context) - Please read :ref:`ssl-security` for more information on best practices. .. note:: @@ -97,6 +91,13 @@ The *strict* parameter was removed. HTTP 0.9-style "Simple Responses" are no longer supported. + .. versionchanged:: 3.5 + This class now uses :func:`ssl._create_default_https_context` if no + context is explicitly provided. This means that it performs all the + necessary certificate and hostname checks by default. To revert to the + previous, unverified, behavior :func:`ssl._create_unverified_context` can + be passed. + .. class:: HTTPResponse(sock, debuglevel=0, method=None, url=None) diff -r c0224ff67cdd Doc/library/urllib.request.rst --- a/Doc/library/urllib.request.rst Mon Oct 13 10:39:41 2014 +0100 +++ b/Doc/library/urllib.request.rst Mon Oct 13 10:26:38 2014 -0700 @@ -62,11 +62,6 @@ *cafile* and *capath* parameters are omitted. This will only work on some non-Windows platforms. - .. warning:: - If neither *cafile* nor *capath* is specified, and *cadefault* is ``False``, - an HTTPS request will not do any verification of the server's - certificate. - For http and https urls, this function returns a :class:`http.client.HTTPResponse` object which has the following :ref:`httpresponse-objects` methods. diff -r c0224ff67cdd Doc/whatsnew/3.5.rst --- a/Doc/whatsnew/3.5.rst Mon Oct 13 10:39:41 2014 +0100 +++ b/Doc/whatsnew/3.5.rst Mon Oct 13 10:26:38 2014 -0700 @@ -97,6 +97,33 @@ .. PEP-sized items next. +.. _pep-476: + +PEP 476: Enabling certificate verification by default for stdlib http clients +============================================================================= + +:mod:`http.client` and modules which use it, such as :mod:`urllib.request` and +:mod:`xmlrpc.client`, will now verify that the server presents a certificate +which is signed by a CA in the platform trust store and whose hostname matches +the hostname being requested by default, significantly improving security for +many applications. + +For applications which require the old previous behavior, they can pass an +alternate context:: + + import urllib.request + import ssl + + # This disables all verification + context = ssl._create_unverified_context() + + # This allows using a specific certificate for the host, which doesn't need + # to be in the trust store + context = ssl.create_default_context(cafile="/path/to/file.crt") + + urllib.request.urlopen("https://invalid-cert", context=context) + + .. _pep-4XX: .. PEP 4XX: Virtual Environments diff -r c0224ff67cdd Lib/http/client.py --- a/Lib/http/client.py Mon Oct 13 10:39:41 2014 +0100 +++ b/Lib/http/client.py Mon Oct 13 10:26:38 2014 -0700 @@ -1267,7 +1267,7 @@ self.key_file = key_file self.cert_file = cert_file if context is None: - context = ssl._create_stdlib_context() + context = ssl._create_default_https_context() will_verify = context.verify_mode != ssl.CERT_NONE if check_hostname is None: check_hostname = will_verify diff -r c0224ff67cdd Lib/ssl.py --- a/Lib/ssl.py Mon Oct 13 10:39:41 2014 +0100 +++ b/Lib/ssl.py Mon Oct 13 10:26:38 2014 -0700 @@ -436,8 +436,7 @@ context.load_default_certs(purpose) return context - -def _create_stdlib_context(protocol=PROTOCOL_SSLv23, *, cert_reqs=None, +def _create_unverified_context(protocol=PROTOCOL_SSLv23, *, cert_reqs=None, check_hostname=False, purpose=Purpose.SERVER_AUTH, certfile=None, keyfile=None, cafile=None, capath=None, cadata=None): @@ -598,6 +597,14 @@ return self._sslobj.version() +# 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 + + class SSLSocket(socket): """This class implements a subtype of socket.socket that wraps the underlying OS socket in an SSL context when necessary, and diff -r c0224ff67cdd Lib/test/test_httplib.py --- a/Lib/test/test_httplib.py Mon Oct 13 10:39:41 2014 +0100 +++ b/Lib/test/test_httplib.py Mon Oct 13 10:26:38 2014 -0700 @@ -1012,14 +1012,35 @@ self.assertIn('Apache', server_string) def test_networked(self): - # Default settings: no cert verification is done + # Default settings: requires a valid cert from a trusted CA + import ssl support.requires('network') with support.transient_internet('svn.python.org'): h = client.HTTPSConnection('svn.python.org', 443) + with self.assertRaises(ssl.SSLError): + h.request('GET', '/') + + def test_networked_noverification(self): + # Switch off cert verification + import ssl + support.requires('network') + with support.transient_internet('svn.python.org'): + context = ssl._create_unverified_context() + h = client.HTTPSConnection('svn.python.org', 443, context=context) h.request('GET', '/') resp = h.getresponse() self._check_svn_python_org(resp) + def test_networked_trusted_by_default_cert(self): + # Default settings: requires a valid cert from a trusted CA + support.requires('network') + with support.transient_internet('www.python.org'): + h = client.HTTPSConnection('www.python.org', 443) + h.request('GET', '/') + resp = h.getresponse() + content_type = resp.getheader('content-type') + self.assertIn('text/html', content_type) + def test_networked_good_cert(self): # We feed a CA cert that validates the server's cert import ssl @@ -1045,6 +1066,14 @@ with self.assertRaises(ssl.SSLError): h.request('GET', '/') + def test_local_unknown_cert(self): + # The custom cert isn't known to the default trust bundle + import ssl + server = self.make_server(CERT_localhost) + h = client.HTTPSConnection('localhost', server.port) + with self.assertRaises(ssl.SSLError): + h.request('GET', '/') + def test_local_good_hostname(self): # The (valid) cert validates the HTTP hostname import ssl @@ -1056,7 +1085,6 @@ h.request('GET', '/nonexistent') resp = h.getresponse() self.assertEqual(resp.status, 404) - del server def test_local_bad_hostname(self): # The (valid) cert doesn't validate the HTTP hostname @@ -1079,7 +1107,6 @@ h.request('GET', '/nonexistent') resp = h.getresponse() self.assertEqual(resp.status, 404) - del server @unittest.skipIf(not hasattr(client, 'HTTPSConnection'), 'http.client.HTTPSConnection not available')