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

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