An interpreted, interactive, object-oriented programming language
CentOS Sources
2017-08-01 71084d584ff953f5463757ec6536406320560b4d
commit | author | age
f63228 1
CS 2 # HG changeset patch
3 # User Benjamin Peterson <benjamin@python.org>
4 # Date 1416764565 21600
5 # Node ID 1882157b298a164291d2b3a8b9525eb0902895f6
6 # Parent  588ebc8fd3daf7307961cd614c4da9525bb67313
7 allow passing cert/ssl information to urllib2.urlopen and httplib.HTTPSConnection
8
9 This is basically a backport of issues #9003 and #22366.
10
11 diff --git a/Doc/library/httplib.rst b/Doc/library/httplib.rst
12 --- a/Doc/library/httplib.rst
13 +++ b/Doc/library/httplib.rst
14 @@ -70,12 +70,25 @@ The module provides the following classe
15        *source_address* was added.
16  
17  
18 -.. class:: HTTPSConnection(host[, port[, key_file[, cert_file[, strict[, timeout[, source_address]]]]]])
19 +.. class:: HTTPSConnection(host[, port[, key_file[, cert_file[, strict[, timeout[, source_address, context, check_hostname]]]]]])
20  
21     A subclass of :class:`HTTPConnection` that uses SSL for communication with
22 -   secure servers.  Default port is ``443``. *key_file* is the name of a PEM
23 -   formatted file that contains your private key. *cert_file* is a PEM formatted
24 -   certificate chain file.
25 +   secure servers.  Default port is ``443``.  If *context* is specified, it must
26 +   be a :class:`ssl.SSLContext` instance describing the various SSL options.
27 +
28 +   *key_file* and *cert_file* are deprecated, please use
29 +   :meth:`ssl.SSLContext.load_cert_chain` instead, or let
30 +   :func:`ssl.create_default_context` select the system's trusted CA
31 +   certificates for you.
32 +
33 +   Please read :ref:`ssl-security` for more information on best practices.
34 +
35 +   .. note::
36 +      If *context* is specified and has a :attr:`~ssl.SSLContext.verify_mode`
37 +      of either :data:`~ssl.CERT_OPTIONAL` or :data:`~ssl.CERT_REQUIRED`, then
38 +      by default *host* is matched against the host name(s) allowed by the
39 +      server's certificate.  If you want to change that behaviour, you can
40 +      explicitly set *check_hostname* to False.
41  
42     .. warning::
43        This does not do any verification of the server's certificate.
44 @@ -88,6 +101,9 @@ The module provides the following classe
45     .. versionchanged:: 2.7
46        *source_address* was added.
47  
48 +   .. versionchanged:: 2.7.9
49 +      *context* and *check_hostname* was added.
50 +
51  
52  .. class:: HTTPResponse(sock, debuglevel=0, strict=0)
53  
54 diff --git a/Lib/test/keycert2.pem b/Lib/test/keycert2.pem
55 new file mode 100644
56 --- /dev/null
57 +++ b/Lib/test/keycert2.pem
58 @@ -0,0 +1,31 @@
59 +-----BEGIN PRIVATE KEY-----
60 +MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBANcLaMB7T/Wi9DBc
61 +PltGzgt8cxsv55m7PQPHMZvn6Ke8xmNqcmEzib8opRwKGrCV6TltKeFlNSg8dwQK
62 +Tl4ktyTkGCVweRQJ37AkBayvEBml5s+QD4vlhqkJPsL/Nsd+fnqngOGc5+59+C6r
63 +s3XpiLlF5ah/z8q92Mnw54nypw1JAgMBAAECgYBE3t2Mj7GbDLZB6rj5yKJioVfI
64 +BD6bSJEQ7bGgqdQkLFwpKMU7BiN+ekjuwvmrRkesYZ7BFgXBPiQrwhU5J28Tpj5B
65 +EOMYSIOHfzdalhxDGM1q2oK9LDFiCotTaSdEzMYadel5rmKXJ0zcK2Jho0PCuECf
66 +tf/ghRxK+h1Hm0tKgQJBAO6MdGDSmGKYX6/5kPDje7we/lSLorSDkYmV0tmVShsc
67 +JxgaGaapazceA/sHL3Myx7Eenkip+yPYDXEDFvAKNDECQQDmxsT9NOp6mo7ISvky
68 +GFr2vVHsJ745BMWoma4rFjPBVnS8RkgK+b2EpDCdZSrQ9zw2r8sKTgrEyrDiGTEg
69 +wJyZAkA8OOc0flYMJg2aHnYR6kwVjPmGHI5h5gk648EMPx0rROs1sXkiUwkHLCOz
70 +HvhCq+Iv+9vX2lnVjbiu/CmxRdIxAkA1YEfzoKeTD+hyXxTgB04Sv5sRGegfXAEz
71 +i8gC4zG5R/vcCA1lrHmvEiLEZL/QcT6WD3bQvVg0SAU9ZkI8pxARAkA7yqMSvP1l
72 +gJXy44R+rzpLYb1/PtiLkIkaKG3x9TUfPnfD2jY09fPkZlfsRU3/uS09IkhSwimV
73 +d5rWoljEfdou
74 +-----END PRIVATE KEY-----
75 +-----BEGIN CERTIFICATE-----
76 +MIICXTCCAcagAwIBAgIJALVQzebTtrXFMA0GCSqGSIb3DQEBBQUAMGIxCzAJBgNV
77 +BAYTAlhZMRcwFQYDVQQHDA5DYXN0bGUgQW50aHJheDEjMCEGA1UECgwaUHl0aG9u
78 +IFNvZnR3YXJlIEZvdW5kYXRpb24xFTATBgNVBAMMDGZha2Vob3N0bmFtZTAeFw0x
79 +NDExMjMxNzAwMDdaFw0yNDExMjAxNzAwMDdaMGIxCzAJBgNVBAYTAlhZMRcwFQYD
80 +VQQHDA5DYXN0bGUgQW50aHJheDEjMCEGA1UECgwaUHl0aG9uIFNvZnR3YXJlIEZv
81 +dW5kYXRpb24xFTATBgNVBAMMDGZha2Vob3N0bmFtZTCBnzANBgkqhkiG9w0BAQEF
82 +AAOBjQAwgYkCgYEA1wtowHtP9aL0MFw+W0bOC3xzGy/nmbs9A8cxm+fop7zGY2py
83 +YTOJvyilHAoasJXpOW0p4WU1KDx3BApOXiS3JOQYJXB5FAnfsCQFrK8QGaXmz5AP
84 +i+WGqQk+wv82x35+eqeA4Zzn7n34LquzdemIuUXlqH/Pyr3YyfDnifKnDUkCAwEA
85 +AaMbMBkwFwYDVR0RBBAwDoIMZmFrZWhvc3RuYW1lMA0GCSqGSIb3DQEBBQUAA4GB
86 +AKuay3vDKfWzt5+ch/HHBsert84ISot4fUjzXDA/oOgTOEjVcSShHxqNShMOW1oA
87 +QYBpBB/5Kx5RkD/w6imhucxt2WQPRgjX4x4bwMipVH/HvFDp03mG51/Cpi1TyZ74
88 +El7qa/Pd4lHhOLzMKBA6503fpeYSFUIBxZbGLqylqRK7
89 +-----END CERTIFICATE-----
90 diff --git a/Lib/test/selfsigned_pythontestdotnet.pem b/Lib/test/selfsigned_pythontestdotnet.pem
91 new file mode 100644
92 --- /dev/null
93 +++ b/Lib/test/selfsigned_pythontestdotnet.pem
94 @@ -0,0 +1,16 @@
95 +-----BEGIN CERTIFICATE-----
96 +MIIChzCCAfCgAwIBAgIJAKGU95wKR8pSMA0GCSqGSIb3DQEBBQUAMHAxCzAJBgNV
97 +BAYTAlhZMRcwFQYDVQQHDA5DYXN0bGUgQW50aHJheDEjMCEGA1UECgwaUHl0aG9u
98 +IFNvZnR3YXJlIEZvdW5kYXRpb24xIzAhBgNVBAMMGnNlbGYtc2lnbmVkLnB5dGhv
99 +bnRlc3QubmV0MB4XDTE0MTEwMjE4MDkyOVoXDTI0MTAzMDE4MDkyOVowcDELMAkG
100 +A1UEBhMCWFkxFzAVBgNVBAcMDkNhc3RsZSBBbnRocmF4MSMwIQYDVQQKDBpQeXRo
101 +b24gU29mdHdhcmUgRm91bmRhdGlvbjEjMCEGA1UEAwwac2VsZi1zaWduZWQucHl0
102 +aG9udGVzdC5uZXQwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANDXQXW9tjyZ
103 +Xt0Iv2tLL1+jinr4wGg36ioLDLFkMf+2Y1GL0v0BnKYG4N1OKlAU15LXGeGer8vm
104 +Sv/yIvmdrELvhAbbo3w4a9TMYQA4XkIVLdvu3mvNOAet+8PMJxn26dbDhG809ALv
105 +EHY57lQsBS3G59RZyBPVqAqmImWNJnVzAgMBAAGjKTAnMCUGA1UdEQQeMByCGnNl
106 +bGYtc2lnbmVkLnB5dGhvbnRlc3QubmV0MA0GCSqGSIb3DQEBBQUAA4GBAIOXmdtM
107 +eG9qzP9TiXW/Gc/zI4cBfdCpC+Y4gOfC9bQUC7hefix4iO3+iZjgy3X/FaRxUUoV
108 +HKiXcXIaWqTSUWp45cSh0MbwZXudp6JIAptzdAhvvCrPKeC9i9GvxsPD4LtDAL97
109 +vSaxQBezA7hdxZd90/EeyMgVZgAnTCnvAWX9
110 +-----END CERTIFICATE-----
111 diff --git a/Lib/test/test_urllib2.py b/Lib/test/test_urllib2.py
112 --- a/Lib/test/test_urllib2.py
113 +++ b/Lib/test/test_urllib2.py
114 @@ -8,6 +8,11 @@ import StringIO
115  import urllib2
116  from urllib2 import Request, OpenerDirector
117  
118 +try:
119 +    import ssl
120 +except ImportError:
121 +    ssl = None
122 +
123  # XXX
124  # Request
125  # CacheFTPHandler (hard to write)
126 @@ -47,6 +52,14 @@ class TrivialTests(unittest.TestCase):
127          for string, list in tests:
128              self.assertEqual(urllib2.parse_http_list(string), list)
129  
130 +    @unittest.skipUnless(ssl, "ssl module required")
131 +    def test_cafile_and_context(self):
132 +        context = ssl.create_default_context()
133 +        with self.assertRaises(ValueError):
134 +            urllib2.urlopen(
135 +                "https://localhost", cafile="/nonexistent/path", context=context
136 +            )
137 +
138  
139  def test_request_headers_dict():
140      """
141 diff --git a/Lib/urllib2.py b/Lib/urllib2.py
142 --- a/Lib/urllib2.py
143 +++ b/Lib/urllib2.py
144 @@ -109,6 +109,14 @@ try:
145  except ImportError:
146      from StringIO import StringIO
147  
148 +# check for SSL
149 +try:
150 +    import ssl
151 +except ImportError:
152 +    _have_ssl = False
153 +else:
154 +    _have_ssl = True
155 +
156  from urllib import (unwrap, unquote, splittype, splithost, quote,
157       addinfourl, splitport, splittag, toBytes,
158       splitattr, ftpwrapper, splituser, splitpasswd, splitvalue)
159 @@ -120,11 +128,30 @@ from urllib import localhost, url2pathna
160  __version__ = sys.version[:3]
161  
162  _opener = None
163 -def urlopen(url, data=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
164 +def urlopen(url, data=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
165 +            cafile=None, capath=None, cadefault=False, context=None):
166      global _opener
167 -    if _opener is None:
168 -        _opener = build_opener()
169 -    return _opener.open(url, data, timeout)
170 +    if cafile or capath or cadefault:
171 +        if context is not None:
172 +            raise ValueError(
173 +                "You can't pass both context and any of cafile, capath, and "
174 +                "cadefault"
175 +            )
176 +        if not _have_ssl:
177 +            raise ValueError('SSL support not available')
178 +        context = ssl._create_stdlib_context(cert_reqs=ssl.CERT_REQUIRED,
179 +                                             cafile=cafile,
180 +                                             capath=capath)
181 +        https_handler = HTTPSHandler(context=context, check_hostname=True)
182 +        opener = build_opener(https_handler)
183 +    elif context:
184 +        https_handler = HTTPSHandler(context=context)
185 +        opener = build_opener(https_handler)
186 +    elif _opener is None:
187 +        _opener = opener = build_opener()
188 +    else:
189 +        opener = _opener
190 +    return opener.open(url, data, timeout)
191  
192  def install_opener(opener):
193      global _opener
194 @@ -1121,7 +1148,7 @@ class AbstractHTTPHandler(BaseHandler):
195  
196          return request
197  
198 -    def do_open(self, http_class, req):
199 +    def do_open(self, http_class, req, **http_conn_args):
200          """Return an addinfourl object for the request, using http_class.
201  
202          http_class must implement the HTTPConnection API from httplib.
203 @@ -1135,7 +1162,8 @@ class AbstractHTTPHandler(BaseHandler):
204          if not host:
205              raise URLError('no host given')
206  
207 -        h = http_class(host, timeout=req.timeout) # will parse host:port
208 +        # will parse host:port
209 +        h = http_class(host, timeout=req.timeout, **http_conn_args)
210          h.set_debuglevel(self._debuglevel)
211  
212          headers = dict(req.unredirected_hdrs)
213 @@ -1203,8 +1231,14 @@ class HTTPHandler(AbstractHTTPHandler):
214  if hasattr(httplib, 'HTTPS'):
215      class HTTPSHandler(AbstractHTTPHandler):
216  
217 +        def __init__(self, debuglevel=0, context=None, check_hostname=None):
218 +            AbstractHTTPHandler.__init__(self, debuglevel)
219 +            self._context = context
220 +            self._check_hostname = check_hostname
221 +
222          def https_open(self, req):
223 -            return self.do_open(httplib.HTTPSConnection, req)
224 +            return self.do_open(httplib.HTTPSConnection, req,
225 +                context=self._context, check_hostname=self._check_hostname)
226  
227          https_request = AbstractHTTPHandler.do_request_
228  
229 diff -up Python-2.7.5/Lib/test/test_urllib2_localnet.py.ctx Python-2.7.5/Lib/test/test_urllib2_localnet.py
230 --- Python-2.7.5/Lib/test/test_urllib2_localnet.py.ctx    2015-03-30 10:13:48.351310552 +0200
231 +++ Python-2.7.5/Lib/test/test_urllib2_localnet.py    2015-03-30 10:14:54.715713679 +0200
232 @@ -1,5 +1,6 @@
233  #!/usr/bin/env python
234  
235 +import os
236  import urlparse
237  import urllib2
238  import BaseHTTPServer
239 @@ -11,6 +12,17 @@ from test import test_support
240  mimetools = test_support.import_module('mimetools', deprecated=True)
241  threading = test_support.import_module('threading')
242  
243 +try:
244 +    import ssl
245 +except ImportError:
246 +    ssl = None
247 +
248 +here = os.path.dirname(__file__)
249 +# Self-signed cert file for 'localhost'
250 +CERT_localhost = os.path.join(here, 'keycert.pem')
251 +# Self-signed cert file for 'fakehostname'
252 +CERT_fakehostname = os.path.join(here, 'keycert2.pem')
253 +
254  # Loopback http server infrastructure
255  
256  class LoopbackHttpServer(BaseHTTPServer.HTTPServer):
257 @@ -25,7 +37,7 @@ class LoopbackHttpServer(BaseHTTPServer.
258  
259          # Set the timeout of our listening socket really low so
260          # that we can stop the server easily.
261 -        self.socket.settimeout(1.0)
262 +        self.socket.settimeout(0.1)
263  
264      def get_request(self):
265          """BaseHTTPServer method, overridden."""
266 @@ -354,6 +366,19 @@ class TestUrlopen(BaseTestCase):
267          urllib2.install_opener(opener)
268          super(TestUrlopen, self).setUp()
269  
270 +    def urlopen(self, url, data=None, **kwargs):
271 +        l = []
272 +        f = urllib2.urlopen(url, data, **kwargs)
273 +        try:
274 +            # Exercise various methods
275 +            l.extend(f.readlines(200))
276 +            l.append(f.readline())
277 +            l.append(f.read(1024))
278 +            l.append(f.read())
279 +        finally:
280 +            f.close()
281 +        return b"".join(l)
282 +
283      def start_server(self, responses):
284          handler = GetRequestHandler(responses)
285  
286 @@ -364,6 +389,16 @@ class TestUrlopen(BaseTestCase):
287          handler.port = port
288          return handler
289  
290 +    def start_https_server(self, responses=None, **kwargs):
291 +        if not hasattr(urllib2, 'HTTPSHandler'):
292 +            self.skipTest('ssl support required')
293 +        from test.ssl_servers import make_https_server
294 +        if responses is None:
295 +            responses = [(200, [], b"we care a bit")]
296 +        handler = GetRequestHandler(responses)
297 +        server = make_https_server(self, handler_class=handler, **kwargs)
298 +        handler.port = server.port
299 +        return handler
300  
301      def test_redirection(self):
302          expected_response = 'We got here...'
303 @@ -434,6 +469,28 @@ class TestUrlopen(BaseTestCase):
304          finally:
305              self.server.stop()
306  
307 +    def test_https(self):
308 +        handler = self.start_https_server()
309 +        context = ssl.create_default_context(cafile=CERT_localhost)
310 +        data = self.urlopen("https://localhost:%s/bizarre" % handler.port, context=context)
311 +        self.assertEqual(data, b"we care a bit")
312 +
313 +    def test_https_with_cafile(self):
314 +        handler = self.start_https_server(certfile=CERT_localhost)
315 +        import ssl
316 +        # Good cert
317 +        data = self.urlopen("https://localhost:%s/bizarre" % handler.port,
318 +                            cafile=CERT_localhost)
319 +        self.assertEqual(data, b"we care a bit")
320 +        # Bad cert
321 +        with self.assertRaises(urllib2.URLError) as cm:
322 +            self.urlopen("https://localhost:%s/bizarre" % handler.port,
323 +                         cafile=CERT_fakehostname)
324 +        # Good cert, but mismatching hostname
325 +        handler = self.start_https_server(certfile=CERT_fakehostname)
326 +        with self.assertRaises(ssl.CertificateError) as cm:
327 +            self.urlopen("https://localhost:%s/bizarre" % handler.port,
328 +                         cafile=CERT_fakehostname)
329  
330      def test_sending_headers(self):
331          handler = self.start_server([(200, [], "we don't care")])
332 diff -up Python-2.7.5/Doc/library/urllib2.rst.ctx Python-2.7.5/Doc/library/urllib2.rst
333 --- Python-2.7.5/Doc/library/urllib2.rst.ctx    2015-03-30 10:20:15.958747076 +0200
334 +++ Python-2.7.5/Doc/library/urllib2.rst    2015-03-30 10:30:46.172779366 +0200
335 @@ -22,13 +22,10 @@ redirections, cookies and more.
336  The :mod:`urllib2` module defines the following functions:
337  
338  
339 -.. function:: urlopen(url[, data][, timeout])
340 +.. function:: urlopen(url[, data[, timeout[, cafile[, capath[, cadefault[, context]]]]])
341  
342     Open the URL *url*, which can be either a string or a :class:`Request` object.
343  
344 -   .. warning::
345 -      HTTPS requests do not do any verification of the server's certificate.
346 -
347     *data* may be a string specifying additional data to send to the server, or
348     ``None`` if no such data is needed.  Currently HTTP requests are the only ones
349     that use *data*; the HTTP request will be a POST instead of a GET when the
350 @@ -41,7 +38,19 @@ The :mod:`urllib2` module defines the fo
351     The optional *timeout* parameter specifies a timeout in seconds for blocking
352     operations like the connection attempt (if not specified, the global default
353     timeout setting will be used).  This actually only works for HTTP, HTTPS and
354 -   FTP connections.
355 +         FTP connections.
356 +
357 +   If *context* is specified, it must be a :class:`ssl.SSLContext` instance
358 +   describing the various SSL options. See :class:`~httplib.HTTPSConnection` for
359 +   more details.
360 +
361 +   The optional *cafile* and *capath* parameters specify a set of trusted CA
362 +   certificates for HTTPS requests.  *cafile* should point to a single file
363 +   containing a bundle of CA certificates, whereas *capath* should point to a
364 +   directory of hashed certificate files.  More information can be found in
365 +   :meth:`ssl.SSLContext.load_verify_locations`.
366 +
367 +   The *cadefault* parameter is ignored.
368  
369     This function returns a file-like object with two additional methods:
370  
371 @@ -66,7 +75,10 @@ The :mod:`urllib2` module defines the fo
372     handled through the proxy.
373  
374     .. versionchanged:: 2.6
375 -      *timeout* was added.
376 +     *timeout* was added.
377 +
378 +   .. versionchanged:: 2.7.9
379 +      *cafile*, *capath*, *cadefault*, and *context* were added.
380  
381  
382  .. function:: install_opener(opener)
383 @@ -280,9 +292,13 @@ The following classes are provided:
384     A class to handle opening of HTTP URLs.
385  
386  
387 -.. class:: HTTPSHandler()
388 +.. class:: HTTPSHandler([debuglevel[, context[, check_hostname]]])
389 +
390 +   A class to handle opening of HTTPS URLs. *context* and *check_hostname* have
391 +   the same meaning as for :class:`httplib.HTTPSConnection`.
392  
393 -   A class to handle opening of HTTPS URLs.
394 +   .. versionchanged:: 2.7.9
395 +      *context* and *check_hostname* were added.
396  
397  
398  .. class:: FileHandler()
399 diff -up Python-2.7.5/Lib/httplib.py.ctx Python-2.7.5/Lib/httplib.py
400 --- Python-2.7.5/Lib/httplib.py.ctx    2015-03-30 10:19:52.551521393 +0200
401 +++ Python-2.7.5/Lib/httplib.py    2015-03-30 10:30:05.045386751 +0200
402 @@ -1159,21 +1159,44 @@ else:
403  
404          def __init__(self, host, port=None, key_file=None, cert_file=None,
405                       strict=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
406 -                     source_address=None):
407 +                     source_address=None, context=None, check_hostname=None):
408              HTTPConnection.__init__(self, host, port, strict, timeout,
409                                      source_address)
410              self.key_file = key_file
411              self.cert_file = cert_file
412 +            if context is None:
413 +                context = ssl.create_default_context()
414 +            will_verify = context.verify_mode != ssl.CERT_NONE
415 +            if check_hostname is None:
416 +                check_hostname = will_verify
417 +            elif check_hostname and not will_verify:
418 +                raise ValueError("check_hostname needs a SSL context with "
419 +                                 "either CERT_OPTIONAL or CERT_REQUIRED")
420 +            if key_file or cert_file:
421 +                context.load_cert_chain(cert_file, key_file)
422 +            self._context = context
423 +            self._check_hostname = check_hostname
424  
425          def connect(self):
426              "Connect to a host on a given (SSL) port."
427  
428 -            sock = socket.create_connection((self.host, self.port),
429 -                                            self.timeout, self.source_address)
430 +            HTTPConnection.connect(self)
431 +
432              if self._tunnel_host:
433 -                self.sock = sock
434 -                self._tunnel()
435 -            self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file)
436 +                server_hostname = self._tunnel_host
437 +            else:
438 +                server_hostname = self.host
439 +            sni_hostname = server_hostname if ssl.HAS_SNI else None
440 +
441 +            self.sock = self._context.wrap_socket(self.sock,
442 +                                                  server_hostname=sni_hostname)
443 +            if not self._context.check_hostname and self._check_hostname:
444 +                try:
445 +                    ssl.match_hostname(self.sock.getpeercert(), server_hostname)
446 +                except Exception:
447 +                    self.sock.shutdown(socket.SHUT_RDWR)
448 +                    self.sock.close()
449 +                    raise
450  
451      __all__.append("HTTPSConnection")
452  
453 diff -up Python-2.7.5/Lib/test/test_httplib.py.ctx Python-2.7.5/Lib/test/test_httplib.py
454 --- Python-2.7.5/Lib/test/test_httplib.py.ctx    2015-03-30 10:19:12.905139139 +0200
455 +++ Python-2.7.5/Lib/test/test_httplib.py    2015-03-30 10:27:41.822017804 +0200
456 @@ -1,6 +1,7 @@
457  import httplib
458  import array
459  import httplib
460 +import os
461  import StringIO
462  import socket
463  import errno
464 @@ -10,6 +11,14 @@ TestCase = unittest.TestCase
465  
466  from test import test_support
467  
468 +here = os.path.dirname(__file__)
469 +# Self-signed cert file for 'localhost'
470 +CERT_localhost = os.path.join(here, 'keycert.pem')
471 +# Self-signed cert file for 'fakehostname'
472 +CERT_fakehostname = os.path.join(here, 'keycert2.pem')
473 +# Self-signed cert file for self-signed.pythontest.net
474 +CERT_selfsigned_pythontestdotnet = os.path.join(here, 'selfsigned_pythontestdotnet.pem')
475 +
476  HOST = test_support.HOST
477  
478  class FakeSocket:
479 @@ -493,40 +502,147 @@ class TimeoutTest(TestCase):
480          httpConn.close()
481  
482  
483 -class HTTPSTimeoutTest(TestCase):
484 +class HTTPSTest(TestCase):
485  # XXX Here should be tests for HTTPS, there isn't any right now!
486 +    def setUp(self):
487 +        if not hasattr(httplib, 'HTTPSConnection'):
488 +            self.skipTest('ssl support required')
489 +
490 +    def make_server(self, certfile):
491 +        from test.ssl_servers import make_https_server
492 +        return make_https_server(self, certfile=certfile)
493  
494      def test_attributes(self):
495 -        # simple test to check it's storing it
496 -        if hasattr(httplib, 'HTTPSConnection'):
497 -            h = httplib.HTTPSConnection(HOST, TimeoutTest.PORT, timeout=30)
498 -            self.assertEqual(h.timeout, 30)
499 +        # simple test to check it's storing the timeout
500 +        h = httplib.HTTPSConnection(HOST, TimeoutTest.PORT, timeout=30)
501 +        self.assertEqual(h.timeout, 30)
502 +
503 +    def test_networked(self):
504 +        # Default settings: requires a valid cert from a trusted CA
505 +        import ssl
506 +        test_support.requires('network')
507 +        with test_support.transient_internet('self-signed.pythontest.net'):
508 +            h = httplib.HTTPSConnection('self-signed.pythontest.net', 443)
509 +            with self.assertRaises(ssl.SSLError) as exc_info:
510 +                h.request('GET', '/')
511 +            self.assertEqual(exc_info.exception.reason, 'CERTIFICATE_VERIFY_FAILED')
512 +
513 +    def test_networked_noverification(self):
514 +        # Switch off cert verification
515 +        import ssl
516 +        test_support.requires('network')
517 +        with test_support.transient_internet('self-signed.pythontest.net'):
518 +            context = ssl._create_stdlib_context()
519 +            h = httplib.HTTPSConnection('self-signed.pythontest.net', 443,
520 +                                        context=context)
521 +            h.request('GET', '/')
522 +            resp = h.getresponse()
523 +            self.assertIn('nginx', resp.getheader('server'))
524 +
525 +    def test_networked_trusted_by_default_cert(self):
526 +        # Default settings: requires a valid cert from a trusted CA
527 +        test_support.requires('network')
528 +        with test_support.transient_internet('www.python.org'):
529 +            h = httplib.HTTPSConnection('www.python.org', 443)
530 +            h.request('GET', '/')
531 +            resp = h.getresponse()
532 +            content_type = resp.getheader('content-type')
533 +            self.assertIn('text/html', content_type)
534 +
535 +    def test_networked_good_cert(self):
536 +        # We feed the server's cert as a validating cert
537 +        import ssl
538 +        test_support.requires('network')
539 +        with test_support.transient_internet('self-signed.pythontest.net'):
540 +            context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
541 +            context.verify_mode = ssl.CERT_REQUIRED
542 +            context.load_verify_locations(CERT_selfsigned_pythontestdotnet)
543 +            h = httplib.HTTPSConnection('self-signed.pythontest.net', 443, context=context)
544 +            h.request('GET', '/')
545 +            resp = h.getresponse()
546 +            server_string = resp.getheader('server')
547 +            self.assertIn('nginx', server_string)
548 +
549 +    def test_networked_bad_cert(self):
550 +        # We feed a "CA" cert that is unrelated to the server's cert
551 +        import ssl
552 +        test_support.requires('network')
553 +        with test_support.transient_internet('self-signed.pythontest.net'):
554 +            context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
555 +            context.verify_mode = ssl.CERT_REQUIRED
556 +            context.load_verify_locations(CERT_localhost)
557 +            h = httplib.HTTPSConnection('self-signed.pythontest.net', 443, context=context)
558 +            with self.assertRaises(ssl.SSLError) as exc_info:
559 +                h.request('GET', '/')
560 +            self.assertEqual(exc_info.exception.reason, 'CERTIFICATE_VERIFY_FAILED')
561 +
562 +    def test_local_unknown_cert(self):
563 +        # The custom cert isn't known to the default trust bundle
564 +        import ssl
565 +        server = self.make_server(CERT_localhost)
566 +        h = httplib.HTTPSConnection('localhost', server.port)
567 +        with self.assertRaises(ssl.SSLError) as exc_info:
568 +            h.request('GET', '/')
569 +        self.assertEqual(exc_info.exception.reason, 'CERTIFICATE_VERIFY_FAILED')
570 +
571 +    def test_local_good_hostname(self):
572 +        # The (valid) cert validates the HTTP hostname
573 +        import ssl
574 +        server = self.make_server(CERT_localhost)
575 +        context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
576 +        context.verify_mode = ssl.CERT_REQUIRED
577 +        context.load_verify_locations(CERT_localhost)
578 +        h = httplib.HTTPSConnection('localhost', server.port, context=context)
579 +        h.request('GET', '/nonexistent')
580 +        resp = h.getresponse()
581 +        self.assertEqual(resp.status, 404)
582 +
583 +    def test_local_bad_hostname(self):
584 +        # The (valid) cert doesn't validate the HTTP hostname
585 +        import ssl
586 +        server = self.make_server(CERT_fakehostname)
587 +        context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
588 +        context.verify_mode = ssl.CERT_REQUIRED
589 +        context.load_verify_locations(CERT_fakehostname)
590 +        h = httplib.HTTPSConnection('localhost', server.port, context=context)
591 +        with self.assertRaises(ssl.CertificateError):
592 +            h.request('GET', '/')
593 +        # Same with explicit check_hostname=True
594 +        h = httplib.HTTPSConnection('localhost', server.port, context=context,
595 +                                   check_hostname=True)
596 +        with self.assertRaises(ssl.CertificateError):
597 +            h.request('GET', '/')
598 +        # With check_hostname=False, the mismatching is ignored
599 +        h = httplib.HTTPSConnection('localhost', server.port, context=context,
600 +                                   check_hostname=False)
601 +        h.request('GET', '/nonexistent')
602 +        resp = h.getresponse()
603 +        self.assertEqual(resp.status, 404)
604  
605 -    @unittest.skipIf(not hasattr(httplib, 'HTTPS'), 'httplib.HTTPS not available')
606      def test_host_port(self):
607          # Check invalid host_port
608  
609 -        # Note that httplib does not accept user:password@ in the host-port.
610          for hp in ("www.python.org:abc", "user:password@www.python.org"):
611 -            self.assertRaises(httplib.InvalidURL, httplib.HTTP, hp)
612 +            self.assertRaises(httplib.InvalidURL, httplib.HTTPSConnection, hp)
613  
614 -        for hp, h, p in (("[fe80::207:e9ff:fe9b]:8000", "fe80::207:e9ff:fe9b",
615 -                          8000),
616 -                         ("pypi.python.org:443", "pypi.python.org", 443),
617 -                         ("pypi.python.org", "pypi.python.org", 443),
618 -                         ("pypi.python.org:", "pypi.python.org", 443),
619 -                         ("[fe80::207:e9ff:fe9b]", "fe80::207:e9ff:fe9b", 443)):
620 -            http = httplib.HTTPS(hp)
621 -            c = http._conn
622 -            if h != c.host:
623 -                self.fail("Host incorrectly parsed: %s != %s" % (h, c.host))
624 -            if p != c.port:
625 -                self.fail("Port incorrectly parsed: %s != %s" % (p, c.host))
626 +        for hp, h, p in (("[fe80::207:e9ff:fe9b]:8000",
627 +                          "fe80::207:e9ff:fe9b", 8000),
628 +                         ("www.python.org:443", "www.python.org", 443),
629 +                         ("www.python.org:", "www.python.org", 443),
630 +                         ("www.python.org", "www.python.org", 443),
631 +                         ("[fe80::207:e9ff:fe9b]", "fe80::207:e9ff:fe9b", 443),
632 +                         ("[fe80::207:e9ff:fe9b]:", "fe80::207:e9ff:fe9b",
633 +                             443)):
634 +            c = httplib.HTTPSConnection(hp)
635 +            self.assertEqual(h, c.host)
636 +            self.assertEqual(p, c.port)
637
638  
639  
640 +@test_support.reap_threads
641  def test_main(verbose=None):
642      test_support.run_unittest(HeaderTests, OfflineTest, BasicTest, TimeoutTest,
643 -                              HTTPSTimeoutTest, SourceAddressTest)
644 +                              HTTPSTest, SourceAddressTest)
645  
646  if __name__ == '__main__':
647      test_main()
648 diff -up Python-2.7.5/Lib/test/test_ssl.py.ctx Python-2.7.5/Lib/test/test_ssl.py
649 --- Python-2.7.5/Lib/test/test_ssl.py.ctx    2015-03-30 10:18:55.677973042 +0200
650 +++ Python-2.7.5/Lib/test/test_ssl.py    2015-03-30 10:22:02.323772604 +0200
651 @@ -14,7 +14,7 @@ import os
652  import errno
653  import pprint
654  import tempfile
655 -import urllib
656 +import urllib2
657  import traceback
658  import weakref
659  import platform
660 @@ -2332,9 +2332,10 @@ else:
661                  d1 = f.read()
662              d2 = ''
663              # now fetch the same data from the HTTPS server
664 -            url = 'https://%s:%d/%s' % (
665 -                HOST, server.port, os.path.split(CERTFILE)[1])
666 -            f = urllib.urlopen(url)
667 +            url = 'https://localhost:%d/%s' % (
668 +                server.port, os.path.split(CERTFILE)[1])
669 +            context = ssl.create_default_context(cafile=CERTFILE)
670 +            f = urllib2.urlopen(url, context=context)
671              try:
672                  dlen = f.info().getheader("content-length")
673                  if dlen and (int(dlen) > 0):