Blame SOURCES/00220-pep466-allow-passing-ssl-urrlib-httplib.patch

ae2451
ae2451
# HG changeset patch
ae2451
# User Benjamin Peterson <benjamin@python.org>
ae2451
# Date 1416764565 21600
ae2451
# Node ID 1882157b298a164291d2b3a8b9525eb0902895f6
ae2451
# Parent  588ebc8fd3daf7307961cd614c4da9525bb67313
ae2451
allow passing cert/ssl information to urllib2.urlopen and httplib.HTTPSConnection
ae2451
ae2451
This is basically a backport of issues #9003 and #22366.
ae2451
ae2451
diff --git a/Doc/library/httplib.rst b/Doc/library/httplib.rst
ae2451
--- a/Doc/library/httplib.rst
ae2451
+++ b/Doc/library/httplib.rst
ae2451
@@ -70,12 +70,25 @@ The module provides the following classe
ae2451
       *source_address* was added.
ae2451
 
ae2451
 
ae2451
-.. class:: HTTPSConnection(host[, port[, key_file[, cert_file[, strict[, timeout[, source_address]]]]]])
ae2451
+.. class:: HTTPSConnection(host[, port[, key_file[, cert_file[, strict[, timeout[, source_address, context, check_hostname]]]]]])
ae2451
 
ae2451
    A subclass of :class:`HTTPConnection` that uses SSL for communication with
ae2451
-   secure servers.  Default port is ``443``. *key_file* is the name of a PEM
ae2451
-   formatted file that contains your private key. *cert_file* is a PEM formatted
ae2451
-   certificate chain file.
ae2451
+   secure servers.  Default port is ``443``.  If *context* is specified, it must
ae2451
+   be a :class:`ssl.SSLContext` instance describing the various SSL options.
ae2451
+
ae2451
+   *key_file* and *cert_file* are deprecated, please use
ae2451
+   :meth:`ssl.SSLContext.load_cert_chain` instead, or let
ae2451
+   :func:`ssl.create_default_context` select the system's trusted CA
ae2451
+   certificates for you.
ae2451
+
ae2451
+   Please read :ref:`ssl-security` for more information on best practices.
ae2451
+
ae2451
+   .. note::
ae2451
+      If *context* is specified and has a :attr:`~ssl.SSLContext.verify_mode`
ae2451
+      of either :data:`~ssl.CERT_OPTIONAL` or :data:`~ssl.CERT_REQUIRED`, then
ae2451
+      by default *host* is matched against the host name(s) allowed by the
ae2451
+      server's certificate.  If you want to change that behaviour, you can
ae2451
+      explicitly set *check_hostname* to False.
ae2451
 
ae2451
    .. warning::
ae2451
       This does not do any verification of the server's certificate.
ae2451
@@ -88,6 +101,9 @@ The module provides the following classe
ae2451
    .. versionchanged:: 2.7
ae2451
       *source_address* was added.
ae2451
 
ae2451
+   .. versionchanged:: 2.7.9
ae2451
+      *context* and *check_hostname* was added.
ae2451
+
ae2451
 
ae2451
 .. class:: HTTPResponse(sock, debuglevel=0, strict=0)
ae2451
 
ae2451
diff --git a/Lib/test/keycert2.pem b/Lib/test/keycert2.pem
ae2451
new file mode 100644
ae2451
--- /dev/null
ae2451
+++ b/Lib/test/keycert2.pem
ae2451
@@ -0,0 +1,31 @@
ae2451
+-----BEGIN PRIVATE KEY-----
ae2451
+MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBANcLaMB7T/Wi9DBc
ae2451
+PltGzgt8cxsv55m7PQPHMZvn6Ke8xmNqcmEzib8opRwKGrCV6TltKeFlNSg8dwQK
ae2451
+Tl4ktyTkGCVweRQJ37AkBayvEBml5s+QD4vlhqkJPsL/Nsd+fnqngOGc5+59+C6r
ae2451
+s3XpiLlF5ah/z8q92Mnw54nypw1JAgMBAAECgYBE3t2Mj7GbDLZB6rj5yKJioVfI
ae2451
+BD6bSJEQ7bGgqdQkLFwpKMU7BiN+ekjuwvmrRkesYZ7BFgXBPiQrwhU5J28Tpj5B
ae2451
+EOMYSIOHfzdalhxDGM1q2oK9LDFiCotTaSdEzMYadel5rmKXJ0zcK2Jho0PCuECf
ae2451
+tf/ghRxK+h1Hm0tKgQJBAO6MdGDSmGKYX6/5kPDje7we/lSLorSDkYmV0tmVShsc
ae2451
+JxgaGaapazceA/sHL3Myx7Eenkip+yPYDXEDFvAKNDECQQDmxsT9NOp6mo7ISvky
ae2451
+GFr2vVHsJ745BMWoma4rFjPBVnS8RkgK+b2EpDCdZSrQ9zw2r8sKTgrEyrDiGTEg
ae2451
+wJyZAkA8OOc0flYMJg2aHnYR6kwVjPmGHI5h5gk648EMPx0rROs1sXkiUwkHLCOz
ae2451
+HvhCq+Iv+9vX2lnVjbiu/CmxRdIxAkA1YEfzoKeTD+hyXxTgB04Sv5sRGegfXAEz
ae2451
+i8gC4zG5R/vcCA1lrHmvEiLEZL/QcT6WD3bQvVg0SAU9ZkI8pxARAkA7yqMSvP1l
ae2451
+gJXy44R+rzpLYb1/PtiLkIkaKG3x9TUfPnfD2jY09fPkZlfsRU3/uS09IkhSwimV
ae2451
+d5rWoljEfdou
ae2451
+-----END PRIVATE KEY-----
ae2451
+-----BEGIN CERTIFICATE-----
ae2451
+MIICXTCCAcagAwIBAgIJALVQzebTtrXFMA0GCSqGSIb3DQEBBQUAMGIxCzAJBgNV
ae2451
+BAYTAlhZMRcwFQYDVQQHDA5DYXN0bGUgQW50aHJheDEjMCEGA1UECgwaUHl0aG9u
ae2451
+IFNvZnR3YXJlIEZvdW5kYXRpb24xFTATBgNVBAMMDGZha2Vob3N0bmFtZTAeFw0x
ae2451
+NDExMjMxNzAwMDdaFw0yNDExMjAxNzAwMDdaMGIxCzAJBgNVBAYTAlhZMRcwFQYD
ae2451
+VQQHDA5DYXN0bGUgQW50aHJheDEjMCEGA1UECgwaUHl0aG9uIFNvZnR3YXJlIEZv
ae2451
+dW5kYXRpb24xFTATBgNVBAMMDGZha2Vob3N0bmFtZTCBnzANBgkqhkiG9w0BAQEF
ae2451
+AAOBjQAwgYkCgYEA1wtowHtP9aL0MFw+W0bOC3xzGy/nmbs9A8cxm+fop7zGY2py
ae2451
+YTOJvyilHAoasJXpOW0p4WU1KDx3BApOXiS3JOQYJXB5FAnfsCQFrK8QGaXmz5AP
ae2451
+i+WGqQk+wv82x35+eqeA4Zzn7n34LquzdemIuUXlqH/Pyr3YyfDnifKnDUkCAwEA
ae2451
+AaMbMBkwFwYDVR0RBBAwDoIMZmFrZWhvc3RuYW1lMA0GCSqGSIb3DQEBBQUAA4GB
ae2451
+AKuay3vDKfWzt5+ch/HHBsert84ISot4fUjzXDA/oOgTOEjVcSShHxqNShMOW1oA
ae2451
+QYBpBB/5Kx5RkD/w6imhucxt2WQPRgjX4x4bwMipVH/HvFDp03mG51/Cpi1TyZ74
ae2451
+El7qa/Pd4lHhOLzMKBA6503fpeYSFUIBxZbGLqylqRK7
ae2451
+-----END CERTIFICATE-----
ae2451
diff --git a/Lib/test/selfsigned_pythontestdotnet.pem b/Lib/test/selfsigned_pythontestdotnet.pem
ae2451
new file mode 100644
ae2451
--- /dev/null
ae2451
+++ b/Lib/test/selfsigned_pythontestdotnet.pem
ae2451
@@ -0,0 +1,16 @@
ae2451
+-----BEGIN CERTIFICATE-----
ae2451
+MIIChzCCAfCgAwIBAgIJAKGU95wKR8pSMA0GCSqGSIb3DQEBBQUAMHAxCzAJBgNV
ae2451
+BAYTAlhZMRcwFQYDVQQHDA5DYXN0bGUgQW50aHJheDEjMCEGA1UECgwaUHl0aG9u
ae2451
+IFNvZnR3YXJlIEZvdW5kYXRpb24xIzAhBgNVBAMMGnNlbGYtc2lnbmVkLnB5dGhv
ae2451
+bnRlc3QubmV0MB4XDTE0MTEwMjE4MDkyOVoXDTI0MTAzMDE4MDkyOVowcDELMAkG
ae2451
+A1UEBhMCWFkxFzAVBgNVBAcMDkNhc3RsZSBBbnRocmF4MSMwIQYDVQQKDBpQeXRo
ae2451
+b24gU29mdHdhcmUgRm91bmRhdGlvbjEjMCEGA1UEAwwac2VsZi1zaWduZWQucHl0
ae2451
+aG9udGVzdC5uZXQwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANDXQXW9tjyZ
ae2451
+Xt0Iv2tLL1+jinr4wGg36ioLDLFkMf+2Y1GL0v0BnKYG4N1OKlAU15LXGeGer8vm
ae2451
+Sv/yIvmdrELvhAbbo3w4a9TMYQA4XkIVLdvu3mvNOAet+8PMJxn26dbDhG809ALv
ae2451
+EHY57lQsBS3G59RZyBPVqAqmImWNJnVzAgMBAAGjKTAnMCUGA1UdEQQeMByCGnNl
ae2451
+bGYtc2lnbmVkLnB5dGhvbnRlc3QubmV0MA0GCSqGSIb3DQEBBQUAA4GBAIOXmdtM
ae2451
+eG9qzP9TiXW/Gc/zI4cBfdCpC+Y4gOfC9bQUC7hefix4iO3+iZjgy3X/FaRxUUoV
ae2451
+HKiXcXIaWqTSUWp45cSh0MbwZXudp6JIAptzdAhvvCrPKeC9i9GvxsPD4LtDAL97
ae2451
+vSaxQBezA7hdxZd90/EeyMgVZgAnTCnvAWX9
ae2451
+-----END CERTIFICATE-----
ae2451
diff --git a/Lib/test/test_urllib2.py b/Lib/test/test_urllib2.py
ae2451
--- a/Lib/test/test_urllib2.py
ae2451
+++ b/Lib/test/test_urllib2.py
ae2451
@@ -8,6 +8,11 @@ import StringIO
ae2451
 import urllib2
ae2451
 from urllib2 import Request, OpenerDirector
ae2451
 
ae2451
+try:
ae2451
+    import ssl
ae2451
+except ImportError:
ae2451
+    ssl = None
ae2451
+
ae2451
 # XXX
ae2451
 # Request
ae2451
 # CacheFTPHandler (hard to write)
ae2451
@@ -47,6 +52,14 @@ class TrivialTests(unittest.TestCase):
ae2451
         for string, list in tests:
ae2451
             self.assertEqual(urllib2.parse_http_list(string), list)
ae2451
 
ae2451
+    @unittest.skipUnless(ssl, "ssl module required")
ae2451
+    def test_cafile_and_context(self):
ae2451
+        context = ssl.create_default_context()
ae2451
+        with self.assertRaises(ValueError):
ae2451
+            urllib2.urlopen(
ae2451
+                "https://localhost", cafile="/nonexistent/path", context=context
ae2451
+            )
ae2451
+
ae2451
 
ae2451
 def test_request_headers_dict():
ae2451
     """
ae2451
diff --git a/Lib/urllib2.py b/Lib/urllib2.py
ae2451
--- a/Lib/urllib2.py
ae2451
+++ b/Lib/urllib2.py
ae2451
@@ -109,6 +109,14 @@ try:
ae2451
 except ImportError:
ae2451
     from StringIO import StringIO
ae2451
 
ae2451
+# check for SSL
ae2451
+try:
ae2451
+    import ssl
ae2451
+except ImportError:
ae2451
+    _have_ssl = False
ae2451
+else:
ae2451
+    _have_ssl = True
ae2451
+
ae2451
 from urllib import (unwrap, unquote, splittype, splithost, quote,
ae2451
      addinfourl, splitport, splittag, toBytes,
ae2451
      splitattr, ftpwrapper, splituser, splitpasswd, splitvalue)
ae2451
@@ -120,11 +128,30 @@ from urllib import localhost, url2pathna
ae2451
 __version__ = sys.version[:3]
ae2451
 
ae2451
 _opener = None
ae2451
-def urlopen(url, data=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
ae2451
+def urlopen(url, data=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
ae2451
+            cafile=None, capath=None, cadefault=False, context=None):
ae2451
     global _opener
ae2451
-    if _opener is None:
ae2451
-        _opener = build_opener()
ae2451
-    return _opener.open(url, data, timeout)
ae2451
+    if cafile or capath or cadefault:
ae2451
+        if context is not None:
ae2451
+            raise ValueError(
ae2451
+                "You can't pass both context and any of cafile, capath, and "
ae2451
+                "cadefault"
ae2451
+            )
ae2451
+        if not _have_ssl:
ae2451
+            raise ValueError('SSL support not available')
ae2451
+        context = ssl._create_stdlib_context(cert_reqs=ssl.CERT_REQUIRED,
ae2451
+                                             cafile=cafile,
ae2451
+                                             capath=capath)
ae2451
+        https_handler = HTTPSHandler(context=context, check_hostname=True)
ae2451
+        opener = build_opener(https_handler)
ae2451
+    elif context:
ae2451
+        https_handler = HTTPSHandler(context=context)
ae2451
+        opener = build_opener(https_handler)
ae2451
+    elif _opener is None:
ae2451
+        _opener = opener = build_opener()
ae2451
+    else:
ae2451
+        opener = _opener
ae2451
+    return opener.open(url, data, timeout)
ae2451
 
ae2451
 def install_opener(opener):
ae2451
     global _opener
ae2451
@@ -1121,7 +1148,7 @@ class AbstractHTTPHandler(BaseHandler):
ae2451
 
ae2451
         return request
ae2451
 
ae2451
-    def do_open(self, http_class, req):
ae2451
+    def do_open(self, http_class, req, **http_conn_args):
ae2451
         """Return an addinfourl object for the request, using http_class.
ae2451
 
ae2451
         http_class must implement the HTTPConnection API from httplib.
ae2451
@@ -1135,7 +1162,8 @@ class AbstractHTTPHandler(BaseHandler):
ae2451
         if not host:
ae2451
             raise URLError('no host given')
ae2451
 
ae2451
-        h = http_class(host, timeout=req.timeout) # will parse host:port
ae2451
+        # will parse host:port
ae2451
+        h = http_class(host, timeout=req.timeout, **http_conn_args)
ae2451
         h.set_debuglevel(self._debuglevel)
ae2451
 
ae2451
         headers = dict(req.unredirected_hdrs)
ae2451
@@ -1203,8 +1231,14 @@ class HTTPHandler(AbstractHTTPHandler):
ae2451
 if hasattr(httplib, 'HTTPS'):
ae2451
     class HTTPSHandler(AbstractHTTPHandler):
ae2451
 
ae2451
+        def __init__(self, debuglevel=0, context=None, check_hostname=None):
ae2451
+            AbstractHTTPHandler.__init__(self, debuglevel)
ae2451
+            self._context = context
ae2451
+            self._check_hostname = check_hostname
ae2451
+
ae2451
         def https_open(self, req):
ae2451
-            return self.do_open(httplib.HTTPSConnection, req)
ae2451
+            return self.do_open(httplib.HTTPSConnection, req,
ae2451
+                context=self._context, check_hostname=self._check_hostname)
ae2451
 
ae2451
         https_request = AbstractHTTPHandler.do_request_
ae2451
 
ae2451
diff -up Python-2.7.5/Lib/test/test_urllib2_localnet.py.ctx Python-2.7.5/Lib/test/test_urllib2_localnet.py
ae2451
--- Python-2.7.5/Lib/test/test_urllib2_localnet.py.ctx	2015-03-30 10:13:48.351310552 +0200
ae2451
+++ Python-2.7.5/Lib/test/test_urllib2_localnet.py	2015-03-30 10:14:54.715713679 +0200
ae2451
@@ -1,5 +1,6 @@
ae2451
 #!/usr/bin/env python
ae2451
 
ae2451
+import os
ae2451
 import urlparse
ae2451
 import urllib2
ae2451
 import BaseHTTPServer
ae2451
@@ -11,6 +12,17 @@ from test import test_support
ae2451
 mimetools = test_support.import_module('mimetools', deprecated=True)
ae2451
 threading = test_support.import_module('threading')
ae2451
 
ae2451
+try:
ae2451
+    import ssl
ae2451
+except ImportError:
ae2451
+    ssl = None
ae2451
+
ae2451
+here = os.path.dirname(__file__)
ae2451
+# Self-signed cert file for 'localhost'
ae2451
+CERT_localhost = os.path.join(here, 'keycert.pem')
ae2451
+# Self-signed cert file for 'fakehostname'
ae2451
+CERT_fakehostname = os.path.join(here, 'keycert2.pem')
ae2451
+
ae2451
 # Loopback http server infrastructure
ae2451
 
ae2451
 class LoopbackHttpServer(BaseHTTPServer.HTTPServer):
ae2451
@@ -25,7 +37,7 @@ class LoopbackHttpServer(BaseHTTPServer.
ae2451
 
ae2451
         # Set the timeout of our listening socket really low so
ae2451
         # that we can stop the server easily.
ae2451
-        self.socket.settimeout(1.0)
ae2451
+        self.socket.settimeout(0.1)
ae2451
 
ae2451
     def get_request(self):
ae2451
         """BaseHTTPServer method, overridden."""
ae2451
@@ -354,6 +366,19 @@ class TestUrlopen(BaseTestCase):
ae2451
         urllib2.install_opener(opener)
ae2451
         super(TestUrlopen, self).setUp()
ae2451
 
ae2451
+    def urlopen(self, url, data=None, **kwargs):
ae2451
+        l = []
ae2451
+        f = urllib2.urlopen(url, data, **kwargs)
ae2451
+        try:
ae2451
+            # Exercise various methods
ae2451
+            l.extend(f.readlines(200))
ae2451
+            l.append(f.readline())
ae2451
+            l.append(f.read(1024))
ae2451
+            l.append(f.read())
ae2451
+        finally:
ae2451
+            f.close()
ae2451
+        return b"".join(l)
ae2451
+
ae2451
     def start_server(self, responses):
ae2451
         handler = GetRequestHandler(responses)
ae2451
 
ae2451
@@ -364,6 +389,16 @@ class TestUrlopen(BaseTestCase):
ae2451
         handler.port = port
ae2451
         return handler
ae2451
 
ae2451
+    def start_https_server(self, responses=None, **kwargs):
ae2451
+        if not hasattr(urllib2, 'HTTPSHandler'):
ae2451
+            self.skipTest('ssl support required')
ae2451
+        from test.ssl_servers import make_https_server
ae2451
+        if responses is None:
ae2451
+            responses = [(200, [], b"we care a bit")]
ae2451
+        handler = GetRequestHandler(responses)
ae2451
+        server = make_https_server(self, handler_class=handler, **kwargs)
ae2451
+        handler.port = server.port
ae2451
+        return handler
ae2451
 
ae2451
     def test_redirection(self):
ae2451
         expected_response = 'We got here...'
ae2451
@@ -434,6 +469,28 @@ class TestUrlopen(BaseTestCase):
ae2451
         finally:
ae2451
             self.server.stop()
ae2451
 
ae2451
+    def test_https(self):
ae2451
+        handler = self.start_https_server()
ae2451
+        context = ssl.create_default_context(cafile=CERT_localhost)
ae2451
+        data = self.urlopen("https://localhost:%s/bizarre" % handler.port, context=context)
ae2451
+        self.assertEqual(data, b"we care a bit")
ae2451
+
ae2451
+    def test_https_with_cafile(self):
ae2451
+        handler = self.start_https_server(certfile=CERT_localhost)
ae2451
+        import ssl
ae2451
+        # Good cert
ae2451
+        data = self.urlopen("https://localhost:%s/bizarre" % handler.port,
ae2451
+                            cafile=CERT_localhost)
ae2451
+        self.assertEqual(data, b"we care a bit")
ae2451
+        # Bad cert
ae2451
+        with self.assertRaises(urllib2.URLError) as cm:
ae2451
+            self.urlopen("https://localhost:%s/bizarre" % handler.port,
ae2451
+                         cafile=CERT_fakehostname)
ae2451
+        # Good cert, but mismatching hostname
ae2451
+        handler = self.start_https_server(certfile=CERT_fakehostname)
ae2451
+        with self.assertRaises(ssl.CertificateError) as cm:
ae2451
+            self.urlopen("https://localhost:%s/bizarre" % handler.port,
ae2451
+                         cafile=CERT_fakehostname)
ae2451
 
ae2451
     def test_sending_headers(self):
ae2451
         handler = self.start_server([(200, [], "we don't care")])
ae2451
diff -up Python-2.7.5/Doc/library/urllib2.rst.ctx Python-2.7.5/Doc/library/urllib2.rst
ae2451
--- Python-2.7.5/Doc/library/urllib2.rst.ctx	2015-03-30 10:20:15.958747076 +0200
ae2451
+++ Python-2.7.5/Doc/library/urllib2.rst	2015-03-30 10:30:46.172779366 +0200
ae2451
@@ -22,13 +22,10 @@ redirections, cookies and more.
ae2451
 The :mod:`urllib2` module defines the following functions:
ae2451
 
ae2451
 
ae2451
-.. function:: urlopen(url[, data][, timeout])
ae2451
+.. function:: urlopen(url[, data[, timeout[, cafile[, capath[, cadefault[, context]]]]])
ae2451
 
ae2451
    Open the URL *url*, which can be either a string or a :class:`Request` object.
ae2451
 
ae2451
-   .. warning::
ae2451
-      HTTPS requests do not do any verification of the server's certificate.
ae2451
-
ae2451
    *data* may be a string specifying additional data to send to the server, or
ae2451
    ``None`` if no such data is needed.  Currently HTTP requests are the only ones
ae2451
    that use *data*; the HTTP request will be a POST instead of a GET when the
ae2451
@@ -41,7 +38,19 @@ The :mod:`urllib2` module defines the fo
ae2451
    The optional *timeout* parameter specifies a timeout in seconds for blocking
ae2451
    operations like the connection attempt (if not specified, the global default
ae2451
    timeout setting will be used).  This actually only works for HTTP, HTTPS and
ae2451
-   FTP connections.
ae2451
+         FTP connections.
ae2451
+
ae2451
+   If *context* is specified, it must be a :class:`ssl.SSLContext` instance
ae2451
+   describing the various SSL options. See :class:`~httplib.HTTPSConnection` for
ae2451
+   more details.
ae2451
+
ae2451
+   The optional *cafile* and *capath* parameters specify a set of trusted CA
ae2451
+   certificates for HTTPS requests.  *cafile* should point to a single file
ae2451
+   containing a bundle of CA certificates, whereas *capath* should point to a
ae2451
+   directory of hashed certificate files.  More information can be found in
ae2451
+   :meth:`ssl.SSLContext.load_verify_locations`.
ae2451
+
ae2451
+   The *cadefault* parameter is ignored.
ae2451
 
ae2451
    This function returns a file-like object with two additional methods:
ae2451
 
ae2451
@@ -66,7 +75,10 @@ The :mod:`urllib2` module defines the fo
ae2451
    handled through the proxy.
ae2451
 
ae2451
    .. versionchanged:: 2.6
ae2451
-      *timeout* was added.
ae2451
+     *timeout* was added.
ae2451
+
ae2451
+   .. versionchanged:: 2.7.9
ae2451
+      *cafile*, *capath*, *cadefault*, and *context* were added.
ae2451
 
ae2451
 
ae2451
 .. function:: install_opener(opener)
ae2451
@@ -280,9 +292,13 @@ The following classes are provided:
ae2451
    A class to handle opening of HTTP URLs.
ae2451
 
ae2451
 
ae2451
-.. class:: HTTPSHandler()
ae2451
+.. class:: HTTPSHandler([debuglevel[, context[, check_hostname]]])
ae2451
+
ae2451
+   A class to handle opening of HTTPS URLs. *context* and *check_hostname* have
ae2451
+   the same meaning as for :class:`httplib.HTTPSConnection`.
ae2451
 
ae2451
-   A class to handle opening of HTTPS URLs.
ae2451
+   .. versionchanged:: 2.7.9
ae2451
+      *context* and *check_hostname* were added.
ae2451
 
ae2451
 
ae2451
 .. class:: FileHandler()
ae2451
diff -up Python-2.7.5/Lib/httplib.py.ctx Python-2.7.5/Lib/httplib.py
ae2451
--- Python-2.7.5/Lib/httplib.py.ctx	2015-03-30 10:19:52.551521393 +0200
ae2451
+++ Python-2.7.5/Lib/httplib.py	2015-03-30 10:30:05.045386751 +0200
ae2451
@@ -1159,21 +1159,44 @@ else:
ae2451
 
ae2451
         def __init__(self, host, port=None, key_file=None, cert_file=None,
ae2451
                      strict=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
ae2451
-                     source_address=None):
ae2451
+                     source_address=None, context=None, check_hostname=None):
ae2451
             HTTPConnection.__init__(self, host, port, strict, timeout,
ae2451
                                     source_address)
ae2451
             self.key_file = key_file
ae2451
             self.cert_file = cert_file
ae2451
+            if context is None:
ae2451
+                context = ssl.create_default_context()
ae2451
+            will_verify = context.verify_mode != ssl.CERT_NONE
ae2451
+            if check_hostname is None:
ae2451
+                check_hostname = will_verify
ae2451
+            elif check_hostname and not will_verify:
ae2451
+                raise ValueError("check_hostname needs a SSL context with "
ae2451
+                                 "either CERT_OPTIONAL or CERT_REQUIRED")
ae2451
+            if key_file or cert_file:
ae2451
+                context.load_cert_chain(cert_file, key_file)
ae2451
+            self._context = context
ae2451
+            self._check_hostname = check_hostname
ae2451
 
ae2451
         def connect(self):
ae2451
             "Connect to a host on a given (SSL) port."
ae2451
 
ae2451
-            sock = socket.create_connection((self.host, self.port),
ae2451
-                                            self.timeout, self.source_address)
ae2451
+            HTTPConnection.connect(self)
ae2451
+
ae2451
             if self._tunnel_host:
ae2451
-                self.sock = sock
ae2451
-                self._tunnel()
ae2451
-            self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file)
ae2451
+                server_hostname = self._tunnel_host
ae2451
+            else:
ae2451
+                server_hostname = self.host
ae2451
+            sni_hostname = server_hostname if ssl.HAS_SNI else None
ae2451
+
ae2451
+            self.sock = self._context.wrap_socket(self.sock,
ae2451
+                                                  server_hostname=sni_hostname)
ae2451
+            if not self._context.check_hostname and self._check_hostname:
ae2451
+                try:
ae2451
+                    ssl.match_hostname(self.sock.getpeercert(), server_hostname)
ae2451
+                except Exception:
ae2451
+                    self.sock.shutdown(socket.SHUT_RDWR)
ae2451
+                    self.sock.close()
ae2451
+                    raise
ae2451
 
ae2451
     __all__.append("HTTPSConnection")
ae2451
 
ae2451
diff -up Python-2.7.5/Lib/test/test_httplib.py.ctx Python-2.7.5/Lib/test/test_httplib.py
ae2451
--- Python-2.7.5/Lib/test/test_httplib.py.ctx	2015-03-30 10:19:12.905139139 +0200
ae2451
+++ Python-2.7.5/Lib/test/test_httplib.py	2015-03-30 10:27:41.822017804 +0200
ae2451
@@ -1,6 +1,7 @@
ae2451
 import httplib
ae2451
 import array
ae2451
 import httplib
ae2451
+import os
ae2451
 import StringIO
ae2451
 import socket
ae2451
 import errno
ae2451
@@ -10,6 +11,14 @@ TestCase = unittest.TestCase
ae2451
 
ae2451
 from test import test_support
ae2451
 
ae2451
+here = os.path.dirname(__file__)
ae2451
+# Self-signed cert file for 'localhost'
ae2451
+CERT_localhost = os.path.join(here, 'keycert.pem')
ae2451
+# Self-signed cert file for 'fakehostname'
ae2451
+CERT_fakehostname = os.path.join(here, 'keycert2.pem')
ae2451
+# Self-signed cert file for self-signed.pythontest.net
ae2451
+CERT_selfsigned_pythontestdotnet = os.path.join(here, 'selfsigned_pythontestdotnet.pem')
ae2451
+
ae2451
 HOST = test_support.HOST
ae2451
 
ae2451
 class FakeSocket:
ae2451
@@ -493,40 +502,147 @@ class TimeoutTest(TestCase):
ae2451
         httpConn.close()
ae2451
 
ae2451
 
ae2451
-class HTTPSTimeoutTest(TestCase):
ae2451
+class HTTPSTest(TestCase):
ae2451
 # XXX Here should be tests for HTTPS, there isn't any right now!
ae2451
+    def setUp(self):
ae2451
+        if not hasattr(httplib, 'HTTPSConnection'):
ae2451
+            self.skipTest('ssl support required')
ae2451
+
ae2451
+    def make_server(self, certfile):
ae2451
+        from test.ssl_servers import make_https_server
ae2451
+        return make_https_server(self, certfile=certfile)
ae2451
 
ae2451
     def test_attributes(self):
ae2451
-        # simple test to check it's storing it
ae2451
-        if hasattr(httplib, 'HTTPSConnection'):
ae2451
-            h = httplib.HTTPSConnection(HOST, TimeoutTest.PORT, timeout=30)
ae2451
-            self.assertEqual(h.timeout, 30)
ae2451
+        # simple test to check it's storing the timeout
ae2451
+        h = httplib.HTTPSConnection(HOST, TimeoutTest.PORT, timeout=30)
ae2451
+        self.assertEqual(h.timeout, 30)
ae2451
+
ae2451
+    def test_networked(self):
ae2451
+        # Default settings: requires a valid cert from a trusted CA
ae2451
+        import ssl
ae2451
+        test_support.requires('network')
ae2451
+        with test_support.transient_internet('self-signed.pythontest.net'):
ae2451
+            h = httplib.HTTPSConnection('self-signed.pythontest.net', 443)
ae2451
+            with self.assertRaises(ssl.SSLError) as exc_info:
ae2451
+                h.request('GET', '/')
ae2451
+            self.assertEqual(exc_info.exception.reason, 'CERTIFICATE_VERIFY_FAILED')
ae2451
+
ae2451
+    def test_networked_noverification(self):
ae2451
+        # Switch off cert verification
ae2451
+        import ssl
ae2451
+        test_support.requires('network')
ae2451
+        with test_support.transient_internet('self-signed.pythontest.net'):
ae2451
+            context = ssl._create_stdlib_context()
ae2451
+            h = httplib.HTTPSConnection('self-signed.pythontest.net', 443,
ae2451
+                                        context=context)
ae2451
+            h.request('GET', '/')
ae2451
+            resp = h.getresponse()
ae2451
+            self.assertIn('nginx', resp.getheader('server'))
ae2451
+
ae2451
+    def test_networked_trusted_by_default_cert(self):
ae2451
+        # Default settings: requires a valid cert from a trusted CA
ae2451
+        test_support.requires('network')
ae2451
+        with test_support.transient_internet('www.python.org'):
ae2451
+            h = httplib.HTTPSConnection('www.python.org', 443)
ae2451
+            h.request('GET', '/')
ae2451
+            resp = h.getresponse()
ae2451
+            content_type = resp.getheader('content-type')
ae2451
+            self.assertIn('text/html', content_type)
ae2451
+
ae2451
+    def test_networked_good_cert(self):
ae2451
+        # We feed the server's cert as a validating cert
ae2451
+        import ssl
ae2451
+        test_support.requires('network')
ae2451
+        with test_support.transient_internet('self-signed.pythontest.net'):
ae2451
+            context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
ae2451
+            context.verify_mode = ssl.CERT_REQUIRED
ae2451
+            context.load_verify_locations(CERT_selfsigned_pythontestdotnet)
ae2451
+            h = httplib.HTTPSConnection('self-signed.pythontest.net', 443, context=context)
ae2451
+            h.request('GET', '/')
ae2451
+            resp = h.getresponse()
ae2451
+            server_string = resp.getheader('server')
ae2451
+            self.assertIn('nginx', server_string)
ae2451
+
ae2451
+    def test_networked_bad_cert(self):
ae2451
+        # We feed a "CA" cert that is unrelated to the server's cert
ae2451
+        import ssl
ae2451
+        test_support.requires('network')
ae2451
+        with test_support.transient_internet('self-signed.pythontest.net'):
ae2451
+            context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
ae2451
+            context.verify_mode = ssl.CERT_REQUIRED
ae2451
+            context.load_verify_locations(CERT_localhost)
ae2451
+            h = httplib.HTTPSConnection('self-signed.pythontest.net', 443, context=context)
ae2451
+            with self.assertRaises(ssl.SSLError) as exc_info:
ae2451
+                h.request('GET', '/')
ae2451
+            self.assertEqual(exc_info.exception.reason, 'CERTIFICATE_VERIFY_FAILED')
ae2451
+
ae2451
+    def test_local_unknown_cert(self):
ae2451
+        # The custom cert isn't known to the default trust bundle
ae2451
+        import ssl
ae2451
+        server = self.make_server(CERT_localhost)
ae2451
+        h = httplib.HTTPSConnection('localhost', server.port)
ae2451
+        with self.assertRaises(ssl.SSLError) as exc_info:
ae2451
+            h.request('GET', '/')
ae2451
+        self.assertEqual(exc_info.exception.reason, 'CERTIFICATE_VERIFY_FAILED')
ae2451
+
ae2451
+    def test_local_good_hostname(self):
ae2451
+        # The (valid) cert validates the HTTP hostname
ae2451
+        import ssl
ae2451
+        server = self.make_server(CERT_localhost)
ae2451
+        context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
ae2451
+        context.verify_mode = ssl.CERT_REQUIRED
ae2451
+        context.load_verify_locations(CERT_localhost)
ae2451
+        h = httplib.HTTPSConnection('localhost', server.port, context=context)
ae2451
+        h.request('GET', '/nonexistent')
ae2451
+        resp = h.getresponse()
ae2451
+        self.assertEqual(resp.status, 404)
ae2451
+
ae2451
+    def test_local_bad_hostname(self):
ae2451
+        # The (valid) cert doesn't validate the HTTP hostname
ae2451
+        import ssl
ae2451
+        server = self.make_server(CERT_fakehostname)
ae2451
+        context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
ae2451
+        context.verify_mode = ssl.CERT_REQUIRED
ae2451
+        context.load_verify_locations(CERT_fakehostname)
ae2451
+        h = httplib.HTTPSConnection('localhost', server.port, context=context)
ae2451
+        with self.assertRaises(ssl.CertificateError):
ae2451
+            h.request('GET', '/')
ae2451
+        # Same with explicit check_hostname=True
ae2451
+        h = httplib.HTTPSConnection('localhost', server.port, context=context,
ae2451
+                                   check_hostname=True)
ae2451
+        with self.assertRaises(ssl.CertificateError):
ae2451
+            h.request('GET', '/')
ae2451
+        # With check_hostname=False, the mismatching is ignored
ae2451
+        h = httplib.HTTPSConnection('localhost', server.port, context=context,
ae2451
+                                   check_hostname=False)
ae2451
+        h.request('GET', '/nonexistent')
ae2451
+        resp = h.getresponse()
ae2451
+        self.assertEqual(resp.status, 404)
ae2451
 
ae2451
-    @unittest.skipIf(not hasattr(httplib, 'HTTPS'), 'httplib.HTTPS not available')
ae2451
     def test_host_port(self):
ae2451
         # Check invalid host_port
ae2451
 
ae2451
-        # Note that httplib does not accept user:password@ in the host-port.
ae2451
         for hp in ("www.python.org:abc", "user:password@www.python.org"):
ae2451
-            self.assertRaises(httplib.InvalidURL, httplib.HTTP, hp)
ae2451
+            self.assertRaises(httplib.InvalidURL, httplib.HTTPSConnection, hp)
ae2451
 
ae2451
-        for hp, h, p in (("[fe80::207:e9ff:fe9b]:8000", "fe80::207:e9ff:fe9b",
ae2451
-                          8000),
ae2451
-                         ("pypi.python.org:443", "pypi.python.org", 443),
ae2451
-                         ("pypi.python.org", "pypi.python.org", 443),
ae2451
-                         ("pypi.python.org:", "pypi.python.org", 443),
ae2451
-                         ("[fe80::207:e9ff:fe9b]", "fe80::207:e9ff:fe9b", 443)):
ae2451
-            http = httplib.HTTPS(hp)
ae2451
-            c = http._conn
ae2451
-            if h != c.host:
ae2451
-                self.fail("Host incorrectly parsed: %s != %s" % (h, c.host))
ae2451
-            if p != c.port:
ae2451
-                self.fail("Port incorrectly parsed: %s != %s" % (p, c.host))
ae2451
+        for hp, h, p in (("[fe80::207:e9ff:fe9b]:8000",
ae2451
+                          "fe80::207:e9ff:fe9b", 8000),
ae2451
+                         ("www.python.org:443", "www.python.org", 443),
ae2451
+                         ("www.python.org:", "www.python.org", 443),
ae2451
+                         ("www.python.org", "www.python.org", 443),
ae2451
+                         ("[fe80::207:e9ff:fe9b]", "fe80::207:e9ff:fe9b", 443),
ae2451
+                         ("[fe80::207:e9ff:fe9b]:", "fe80::207:e9ff:fe9b",
ae2451
+                             443)):
ae2451
+            c = httplib.HTTPSConnection(hp)
ae2451
+            self.assertEqual(h, c.host)
ae2451
+            self.assertEqual(p, c.port)
ae2451
+ 
ae2451
 
ae2451
 
ae2451
+@test_support.reap_threads
ae2451
 def test_main(verbose=None):
ae2451
     test_support.run_unittest(HeaderTests, OfflineTest, BasicTest, TimeoutTest,
ae2451
-                              HTTPSTimeoutTest, SourceAddressTest)
ae2451
+                              HTTPSTest, SourceAddressTest)
ae2451
 
ae2451
 if __name__ == '__main__':
ae2451
     test_main()
ae2451
diff -up Python-2.7.5/Lib/test/test_ssl.py.ctx Python-2.7.5/Lib/test/test_ssl.py
ae2451
--- Python-2.7.5/Lib/test/test_ssl.py.ctx	2015-03-30 10:18:55.677973042 +0200
ae2451
+++ Python-2.7.5/Lib/test/test_ssl.py	2015-03-30 10:22:02.323772604 +0200
ae2451
@@ -14,7 +14,7 @@ import os
ae2451
 import errno
ae2451
 import pprint
ae2451
 import tempfile
ae2451
-import urllib
ae2451
+import urllib2
ae2451
 import traceback
ae2451
 import weakref
ae2451
 import platform
ae2451
@@ -2332,9 +2332,10 @@ else:
ae2451
                 d1 = f.read()
ae2451
             d2 = ''
ae2451
             # now fetch the same data from the HTTPS server
ae2451
-            url = 'https://%s:%d/%s' % (
ae2451
-                HOST, server.port, os.path.split(CERTFILE)[1])
ae2451
-            f = urllib.urlopen(url)
ae2451
+            url = 'https://localhost:%d/%s' % (
ae2451
+                server.port, os.path.split(CERTFILE)[1])
ae2451
+            context = ssl.create_default_context(cafile=CERTFILE)
ae2451
+            f = urllib2.urlopen(url, context=context)
ae2451
             try:
ae2451
                 dlen = f.info().getheader("content-length")
ae2451
                 if dlen and (int(dlen) > 0):