~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

(vila) Revise legal option names to be less drastic. (Vincent Ladeuil)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2013, 2016 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
56
56
import urllib2
57
57
import urlparse
58
58
import re
59
 
import ssl
60
59
import sys
61
60
import time
62
61
 
71
70
    transport,
72
71
    ui,
73
72
    urlutils,
74
 
)
75
 
 
76
 
try:
77
 
    _ = (ssl.match_hostname, ssl.CertificateError)
78
 
except AttributeError:
79
 
    # Provide fallbacks for python < 2.7.9
80
 
    def match_hostname(cert, host):
81
 
        trace.warning(
82
 
            '%s cannot be verified, https certificates verification is only'
83
 
            ' available for python versions >= 2.7.9' % (host,))
84
 
    ssl.match_hostname = match_hostname
85
 
    ssl.CertificateError = ValueError
 
73
    )
 
74
lazy_import.lazy_import(globals(), """
 
75
import ssl
 
76
""")
86
77
 
87
78
 
88
79
# Note for packagers: if there is no package providing certs for your platform,
89
80
# the curl project produces http://curl.haxx.se/ca/cacert.pem weekly.
90
81
_ssl_ca_certs_known_locations = [
91
 
    u'/etc/ssl/certs/ca-certificates.crt',  # Ubuntu/debian/gentoo
92
 
    u'/etc/pki/tls/certs/ca-bundle.crt',  # Fedora/CentOS/RH
93
 
    u'/etc/ssl/ca-bundle.pem',  # OpenSuse
94
 
    u'/etc/ssl/cert.pem',  # OpenSuse
95
 
    u"/usr/local/share/certs/ca-root-nss.crt",  # FreeBSD
 
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
96
87
    # XXX: Needs checking, can't trust the interweb ;) -- vila 2012-01-25
97
 
    u'/etc/openssl/certs/ca-certificates.crt',  # Solaris
98
 
]
99
 
 
100
 
 
 
88
    u'/etc/openssl/certs/ca-certificates.crt', # Solaris
 
89
    ]
101
90
def default_ca_certs():
102
91
    if sys.platform == 'win32':
103
92
        return os.path.join(os.path.dirname(sys.executable), u"cacert.pem")
126
115
def cert_reqs_from_store(unicode_str):
127
116
    import ssl
128
117
    try:
129
 
        return {"required": ssl.CERT_REQUIRED,
130
 
                "none": ssl.CERT_NONE}[unicode_str]
 
118
        return {
 
119
            "required": ssl.CERT_REQUIRED,
 
120
            "none": ssl.CERT_NONE
 
121
            }[unicode_str]
131
122
    except KeyError:
132
123
        raise ValueError("invalid value %s" % unicode_str)
133
124
 
134
 
 
135
125
def default_ca_reqs():
136
126
    if sys.platform in ('win32', 'darwin'):
137
127
        # FIXME: Once we get a native access to root certificates there, this
141
131
        return u'required'
142
132
 
143
133
opt_ssl_ca_certs = config.Option('ssl.ca_certs',
144
 
                                 from_unicode=ca_certs_from_store,
145
 
                                 default=default_ca_certs,
146
 
                                 invalid='warning',
147
 
                                 help="""\
 
134
        from_unicode=ca_certs_from_store,
 
135
        default=default_ca_certs,
 
136
        invalid='warning',
 
137
        help="""\
148
138
Path to certification authority certificates to trust.
149
139
 
150
140
This should be a valid path to a bundle containing all root Certificate
154
144
""")
155
145
 
156
146
opt_ssl_cert_reqs = config.Option('ssl.cert_reqs',
157
 
                                  default=default_ca_reqs,
158
 
                                  from_unicode=cert_reqs_from_store,
159
 
                                  invalid='error',
160
 
                                  help="""\
 
147
        default=default_ca_reqs,
 
148
        from_unicode=cert_reqs_from_store,
 
149
        invalid='error',
 
150
        help="""\
161
151
Whether to require a certificate from the remote side. (default:required)
162
152
 
163
153
Possible values:
408
398
        self._wrap_socket_for_reporting(self.sock)
409
399
 
410
400
 
 
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")
 
461
 
 
462
 
411
463
class HTTPSConnection(AbstractHTTPConnection, httplib.HTTPSConnection):
412
464
 
413
465
    def __init__(self, host, port=None, key_file=None, cert_file=None,
451
503
                    "'bzr help ssl.ca_certs' for more information on setting "
452
504
                    "trusted CAs.")
453
505
        try:
454
 
            ssl_sock = ssl.wrap_socket(
455
 
                self.sock, self.key_file, self.cert_file,
 
506
            ssl_sock = ssl.wrap_socket(self.sock, self.key_file, self.cert_file,
456
507
                cert_reqs=cert_reqs, ca_certs=ca_certs)
457
 
        except ssl.SSLError:
 
508
        except ssl.SSLError, e:
458
509
            trace.note(
459
510
                "\n"
460
511
                "See `bzr help ssl.ca_certs` for how to specify trusted CA"
464
515
            raise
465
516
        if cert_reqs == ssl.CERT_REQUIRED:
466
517
            peer_cert = ssl_sock.getpeercert()
467
 
            ssl.match_hostname(peer_cert, host)
 
518
            match_hostname(peer_cert, host)
468
519
 
469
520
        # Wrap the ssl socket before anybody use it
470
521
        self._wrap_socket_for_reporting(ssl_sock)
782
833
                    % (request, request.connection.sock.getsockname())
783
834
            response = connection.getresponse()
784
835
            convert_to_addinfourl = True
785
 
        except (ssl.SSLError, ssl.CertificateError):
 
836
        except (ssl.SSLError, errors.CertificateError):
786
837
            # Something is wrong with either the certificate or the hostname,
787
838
            # re-trying won't help
788
839
            raise