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

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