~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: 2016-01-31 13:36:59 UTC
  • mfrom: (6613.1.5 1538480-match-hostname)
  • Revision ID: pqm@pqm.ubuntu.com-20160131133659-ouy92ee2wlv9xz8m
(vila) Use ssl.match_hostname instead of our own. (Vincent Ladeuil)

Show diffs side-by-side

added added

removed removed

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