~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/http/_urllib2_wrappers.py

  • Committer: Patch Queue Manager
  • Date: 2015-12-17 18:39:00 UTC
  • mfrom: (6606.1.2 fix-float)
  • Revision ID: pqm@pqm.ubuntu.com-20151217183900-0719du2uv1kwu3lc
(vila) Inline testtools private method to fix an issue in xenial (the
 private implementation has changed in an backward incompatible way).
 (Jelmer Vernooij)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2011 Canonical Ltd
 
1
# Copyright (C) 2006-2012 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
 
"""Implementaion of urllib2 tailored to bzr needs
 
17
"""Implementation of urllib2 tailored to bzr needs
18
18
 
19
19
This file complements the urllib2 class hierarchy with custom classes.
20
20
 
36
36
request (see AbstractHTTPHandler.do_open).
37
37
"""
38
38
 
 
39
from __future__ import absolute_import
 
40
 
39
41
DEBUG = 0
40
42
 
41
43
# FIXME: Oversimplifying, two kind of exceptions should be
48
50
 
49
51
import errno
50
52
import httplib
51
 
try:
52
 
    import kerberos
53
 
except ImportError:
54
 
    have_kerberos = False
55
 
else:
56
 
    have_kerberos = True
 
53
import os
57
54
import socket
58
55
import urllib
59
56
import urllib2
67
64
    config,
68
65
    debug,
69
66
    errors,
 
67
    lazy_import,
70
68
    osutils,
71
69
    trace,
72
70
    transport,
 
71
    ui,
73
72
    urlutils,
74
73
    )
 
74
lazy_import.lazy_import(globals(), """
 
75
import ssl
 
76
""")
 
77
 
 
78
 
 
79
# Note for packagers: if there is no package providing certs for your platform,
 
80
# the curl project produces http://curl.haxx.se/ca/cacert.pem weekly.
 
81
_ssl_ca_certs_known_locations = [
 
82
    u'/etc/ssl/certs/ca-certificates.crt', # Ubuntu/debian/gentoo
 
83
    u'/etc/pki/tls/certs/ca-bundle.crt', # Fedora/CentOS/RH
 
84
    u'/etc/ssl/ca-bundle.pem', # OpenSuse
 
85
    u'/etc/ssl/cert.pem', # OpenSuse
 
86
    u"/usr/local/share/certs/ca-root-nss.crt", # FreeBSD
 
87
    # XXX: Needs checking, can't trust the interweb ;) -- vila 2012-01-25
 
88
    u'/etc/openssl/certs/ca-certificates.crt', # Solaris
 
89
    ]
 
90
def default_ca_certs():
 
91
    if sys.platform == 'win32':
 
92
        return os.path.join(os.path.dirname(sys.executable), u"cacert.pem")
 
93
    elif sys.platform == 'darwin':
 
94
        # FIXME: Needs some default value for osx, waiting for osx installers
 
95
        # guys feedback -- vila 2012-01-25
 
96
        pass
 
97
    else:
 
98
        # Try known locations for friendly OSes providing the root certificates
 
99
        # without making them hard to use for any https client.
 
100
        for path in _ssl_ca_certs_known_locations:
 
101
            if os.path.exists(path):
 
102
                # First found wins
 
103
                return path
 
104
    # A default path that makes sense and will be mentioned in the error
 
105
    # presented to the user, even if not correct for all platforms
 
106
    return _ssl_ca_certs_known_locations[0]
 
107
 
 
108
 
 
109
def ca_certs_from_store(path):
 
110
    if not os.path.exists(path):
 
111
        raise ValueError("ca certs path %s does not exist" % path)
 
112
    return path
 
113
 
 
114
 
 
115
def cert_reqs_from_store(unicode_str):
 
116
    import ssl
 
117
    try:
 
118
        return {
 
119
            "required": ssl.CERT_REQUIRED,
 
120
            "none": ssl.CERT_NONE
 
121
            }[unicode_str]
 
122
    except KeyError:
 
123
        raise ValueError("invalid value %s" % unicode_str)
 
124
 
 
125
def default_ca_reqs():
 
126
    if sys.platform in ('win32', 'darwin'):
 
127
        # FIXME: Once we get a native access to root certificates there, this
 
128
        # won't needed anymore. See http://pad.lv/920455 -- vila 2012-02-15
 
129
        return u'none'
 
130
    else:
 
131
        return u'required'
 
132
 
 
133
opt_ssl_ca_certs = config.Option('ssl.ca_certs',
 
134
        from_unicode=ca_certs_from_store,
 
135
        default=default_ca_certs,
 
136
        invalid='warning',
 
137
        help="""\
 
138
Path to certification authority certificates to trust.
 
139
 
 
140
This should be a valid path to a bundle containing all root Certificate
 
141
Authorities used to verify an https server certificate.
 
142
 
 
143
Use ssl.cert_reqs=none to disable certificate verification.
 
144
""")
 
145
 
 
146
opt_ssl_cert_reqs = config.Option('ssl.cert_reqs',
 
147
        default=default_ca_reqs,
 
148
        from_unicode=cert_reqs_from_store,
 
149
        invalid='error',
 
150
        help="""\
 
151
Whether to require a certificate from the remote side. (default:required)
 
152
 
 
153
Possible values:
 
154
 * none: Certificates ignored
 
155
 * required: Certificates required and validated
 
156
""")
 
157
 
 
158
checked_kerberos = False
 
159
kerberos = None
75
160
 
76
161
 
77
162
class addinfourl(urllib2.addinfourl):
299
384
 
300
385
    # XXX: Needs refactoring at the caller level.
301
386
    def __init__(self, host, port=None, proxied_host=None,
302
 
                 report_activity=None):
 
387
                 report_activity=None, ca_certs=None):
303
388
        AbstractHTTPConnection.__init__(self, report_activity=report_activity)
304
389
        # Use strict=True since we don't support HTTP/0.9
305
390
        httplib.HTTPConnection.__init__(self, host, port, strict=True)
306
391
        self.proxied_host = proxied_host
 
392
        # ca_certs is ignored, it's only relevant for https
307
393
 
308
394
    def connect(self):
309
395
        if 'http' in debug.debug_flags:
312
398
        self._wrap_socket_for_reporting(self.sock)
313
399
 
314
400
 
315
 
# Build the appropriate socket wrapper for ssl
316
 
try:
317
 
    # python 2.6 introduced a better ssl package
318
 
    import ssl
319
 
    _ssl_wrap_socket = ssl.wrap_socket
320
 
except ImportError:
321
 
    # python versions prior to 2.6 don't have ssl and ssl.wrap_socket instead
322
 
    # they use httplib.FakeSocket
323
 
    def _ssl_wrap_socket(sock, key_file, cert_file):
324
 
        ssl_sock = socket.ssl(sock, key_file, cert_file)
325
 
        return httplib.FakeSocket(sock, ssl_sock)
 
401
# These two methods were imported from Python 3.2's ssl module
 
402
 
 
403
def _dnsname_to_pat(dn, max_wildcards=1):
 
404
    pats = []
 
405
    for frag in dn.split(r'.'):
 
406
        if frag.count('*') > max_wildcards:
 
407
            # Python Issue #17980: avoid denials of service by refusing more
 
408
            # than one wildcard per fragment.  A survery of established
 
409
            # policy among SSL implementations showed it to be a
 
410
            # reasonable choice.
 
411
            raise ValueError(
 
412
                "too many wildcards in certificate DNS name: " + repr(dn))
 
413
        if frag == '*':
 
414
            # When '*' is a fragment by itself, it matches a non-empty dotless
 
415
            # fragment.
 
416
            pats.append('[^.]+')
 
417
        else:
 
418
            # Otherwise, '*' matches any dotless fragment.
 
419
            frag = re.escape(frag)
 
420
            pats.append(frag.replace(r'\*', '[^.]*'))
 
421
    return re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)
 
422
 
 
423
 
 
424
def match_hostname(cert, hostname):
 
425
    """Verify that *cert* (in decoded format as returned by
 
426
    SSLSocket.getpeercert()) matches the *hostname*.  RFC 2818 rules
 
427
    are mostly followed, but IP addresses are not accepted for *hostname*.
 
428
 
 
429
    CertificateError is raised on failure. On success, the function
 
430
    returns nothing.
 
431
    """
 
432
    if not cert:
 
433
        raise ValueError("empty or no certificate")
 
434
    dnsnames = []
 
435
    san = cert.get('subjectAltName', ())
 
436
    for key, value in san:
 
437
        if key == 'DNS':
 
438
            if _dnsname_to_pat(value).match(hostname):
 
439
                return
 
440
            dnsnames.append(value)
 
441
    if not san:
 
442
        # The subject is only checked when subjectAltName is empty
 
443
        for sub in cert.get('subject', ()):
 
444
            for key, value in sub:
 
445
                # XXX according to RFC 2818, the most specific Common Name
 
446
                # must be used.
 
447
                if key == 'commonName':
 
448
                    if _dnsname_to_pat(value).match(hostname):
 
449
                        return
 
450
                    dnsnames.append(value)
 
451
    if len(dnsnames) > 1:
 
452
        raise errors.CertificateError(
 
453
            "hostname %r doesn't match either of %s"
 
454
            % (hostname, ', '.join(map(repr, dnsnames))))
 
455
    elif len(dnsnames) == 1:
 
456
        raise errors.CertificateError("hostname %r doesn't match %r" %
 
457
                                      (hostname, dnsnames[0]))
 
458
    else:
 
459
        raise errors.CertificateError("no appropriate commonName or "
 
460
            "subjectAltName fields were found")
326
461
 
327
462
 
328
463
class HTTPSConnection(AbstractHTTPConnection, httplib.HTTPSConnection):
329
464
 
330
465
    def __init__(self, host, port=None, key_file=None, cert_file=None,
331
466
                 proxied_host=None,
332
 
                 report_activity=None):
 
467
                 report_activity=None, ca_certs=None):
333
468
        AbstractHTTPConnection.__init__(self, report_activity=report_activity)
334
469
        # Use strict=True since we don't support HTTP/0.9
335
470
        httplib.HTTPSConnection.__init__(self, host, port,
336
471
                                         key_file, cert_file, strict=True)
337
472
        self.proxied_host = proxied_host
 
473
        self.ca_certs = ca_certs
338
474
 
339
475
    def connect(self):
340
476
        if 'http' in debug.debug_flags:
345
481
            self.connect_to_origin()
346
482
 
347
483
    def connect_to_origin(self):
348
 
        ssl_sock = _ssl_wrap_socket(self.sock, self.key_file, self.cert_file)
 
484
        # FIXME JRV 2011-12-18: Use location config here?
 
485
        config_stack = config.GlobalStack()
 
486
        cert_reqs = config_stack.get('ssl.cert_reqs')
 
487
        if self.proxied_host is not None:
 
488
            host = self.proxied_host.split(":", 1)[0]
 
489
        else:
 
490
            host = self.host
 
491
        if cert_reqs == ssl.CERT_NONE:
 
492
            ui.ui_factory.show_user_warning('not_checking_ssl_cert', host=host)
 
493
            ui.ui_factory.suppressed_warnings.add('not_checking_ssl_cert')
 
494
            ca_certs = None
 
495
        else:
 
496
            if self.ca_certs is None:
 
497
                ca_certs = config_stack.get('ssl.ca_certs')
 
498
            else:
 
499
                ca_certs = self.ca_certs
 
500
            if ca_certs is None:
 
501
                trace.warning(
 
502
                    "No valid trusted SSL CA certificates file set. See "
 
503
                    "'bzr help ssl.ca_certs' for more information on setting "
 
504
                    "trusted CAs.")
 
505
        try:
 
506
            ssl_sock = ssl.wrap_socket(self.sock, self.key_file, self.cert_file,
 
507
                cert_reqs=cert_reqs, ca_certs=ca_certs)
 
508
        except ssl.SSLError, e:
 
509
            trace.note(
 
510
                "\n"
 
511
                "See `bzr help ssl.ca_certs` for how to specify trusted CA"
 
512
                "certificates.\n"
 
513
                "Pass -Ossl.cert_reqs=none to disable certificate "
 
514
                "verification entirely.\n")
 
515
            raise
 
516
        if cert_reqs == ssl.CERT_REQUIRED:
 
517
            peer_cert = ssl_sock.getpeercert()
 
518
            match_hostname(peer_cert, host)
 
519
 
349
520
        # Wrap the ssl socket before anybody use it
350
521
        self._wrap_socket_for_reporting(ssl_sock)
351
522
 
453
624
 
454
625
    handler_order = 1000 # after all pre-processings
455
626
 
456
 
    def __init__(self, report_activity=None):
 
627
    def __init__(self, report_activity=None, ca_certs=None):
457
628
        self._report_activity = report_activity
 
629
        self.ca_certs = ca_certs
458
630
 
459
631
    def create_connection(self, request, http_connection_class):
460
632
        host = request.get_host()
468
640
        try:
469
641
            connection = http_connection_class(
470
642
                host, proxied_host=request.proxied_host,
471
 
                report_activity=self._report_activity)
 
643
                report_activity=self._report_activity,
 
644
                ca_certs=self.ca_certs)
472
645
        except httplib.InvalidURL, exception:
473
646
            # There is only one occurrence of InvalidURL in httplib
474
647
            raise errors.InvalidURL(request.get_full_url(),
660
833
                    % (request, request.connection.sock.getsockname())
661
834
            response = connection.getresponse()
662
835
            convert_to_addinfourl = True
 
836
        except (ssl.SSLError, errors.CertificateError):
 
837
            # Something is wrong with either the certificate or the hostname,
 
838
            # re-trying won't help
 
839
            raise
663
840
        except (socket.gaierror, httplib.BadStatusLine, httplib.UnknownProtocol,
664
841
                socket.error, httplib.HTTPException):
665
842
            response = self.retry_or_raise(http_class, request, first_try)
1325
1502
 
1326
1503
    def _auth_match_kerberos(self, auth):
1327
1504
        """Try to create a GSSAPI response for authenticating against a host."""
1328
 
        if not have_kerberos:
 
1505
        global kerberos, checked_kerberos
 
1506
        if kerberos is None and not checked_kerberos:
 
1507
            try:
 
1508
                import kerberos
 
1509
            except ImportError:
 
1510
                kerberos = None
 
1511
            checked_kerberos = True
 
1512
        if kerberos is None:
1329
1513
            return None
1330
1514
        ret, vc = kerberos.authGSSClientInit("HTTP@%(host)s" % auth)
1331
1515
        if ret < 1:
1650
1834
                 connection=ConnectionHandler,
1651
1835
                 redirect=HTTPRedirectHandler,
1652
1836
                 error=HTTPErrorProcessor,
1653
 
                 report_activity=None):
 
1837
                 report_activity=None,
 
1838
                 ca_certs=None):
1654
1839
        self._opener = urllib2.build_opener(
1655
 
            connection(report_activity=report_activity),
 
1840
            connection(report_activity=report_activity, ca_certs=ca_certs),
1656
1841
            redirect, error,
1657
1842
            ProxyHandler(),
1658
1843
            HTTPBasicAuthHandler(),