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
17
"""Implementaion of urllib2 tailored to bzr needs
17
"""Implementation of urllib2 tailored to bzr needs
19
19
This file complements the urllib2 class hierarchy with custom classes.
73
lazy_import.lazy_import(globals(), """
78
# Note for packagers: if there is no package providing certs for your platform,
79
# the curl project produces http://curl.haxx.se/ca/cacert.pem weekly.
80
_ssl_ca_certs_known_locations = [
81
u'/etc/ssl/certs/ca-certificates.crt', # Ubuntu/debian/gentoo
82
u'/etc/pki/tls/certs/ca-bundle.crt', # Fedora/CentOS/RH
83
u'/etc/ssl/ca-bundle.pem', # OpenSuse
84
u'/etc/ssl/cert.pem', # OpenSuse
85
u"/usr/local/share/certs/ca-root-nss.crt", # FreeBSD
86
# XXX: Needs checking, can't trust the interweb ;) -- vila 2012-01-25
87
u'/etc/openssl/certs/ca-certificates.crt', # Solaris
90
def default_ca_certs():
91
if sys.platform == 'win32':
92
return os.path.join(os.path.dirname(sys.executable), u"ca_bundle.crt")
93
elif sys.platform == 'darwin':
94
# FIXME: Needs some default value for osx, waiting for osx installers
95
# guys feedback -- vila 2012-01-25
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):
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]
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)
115
def cert_reqs_from_store(unicode_str):
119
"required": ssl.CERT_REQUIRED,
120
"none": ssl.CERT_NONE
123
raise ValueError("invalid value %s" % unicode_str)
126
opt_ssl_ca_certs = config.Option('ssl.ca_certs',
127
from_unicode=ca_certs_from_store,
128
default=default_ca_certs,
131
Path to certification authority certificates to trust.
133
This should be a valid path to a bundle containing all root Certificate
134
Authorities used to verify an https server certificate.
136
Use ssl.cert_reqs=none to disable certificate verification.
139
opt_ssl_cert_reqs = config.Option('ssl.cert_reqs',
141
from_unicode=cert_reqs_from_store,
144
Whether to require a certificate from the remote side. (default:required)
147
* none: Certificates ignored
148
* required: Certificates required and validated
73
151
checked_kerberos = False
300
378
# XXX: Needs refactoring at the caller level.
301
379
def __init__(self, host, port=None, proxied_host=None,
302
report_activity=None):
380
report_activity=None, ca_certs=None):
303
381
AbstractHTTPConnection.__init__(self, report_activity=report_activity)
304
382
# Use strict=True since we don't support HTTP/0.9
305
383
httplib.HTTPConnection.__init__(self, host, port, strict=True)
306
384
self.proxied_host = proxied_host
385
# ca_certs is ignored, it's only relevant for https
308
387
def connect(self):
309
388
if 'http' in debug.debug_flags:
312
391
self._wrap_socket_for_reporting(self.sock)
315
# Build the appropriate socket wrapper for ssl
317
# python 2.6 introduced a better ssl package
319
_ssl_wrap_socket = ssl.wrap_socket
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)
394
# These two methods were imported from Python 3.2's ssl module
396
def _dnsname_to_pat(dn):
398
for frag in dn.split(r'.'):
400
# When '*' is a fragment by itself, it matches a non-empty dotless
404
# Otherwise, '*' matches any dotless fragment.
405
frag = re.escape(frag)
406
pats.append(frag.replace(r'\*', '[^.]*'))
407
return re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)
410
def match_hostname(cert, hostname):
411
"""Verify that *cert* (in decoded format as returned by
412
SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 rules
413
are mostly followed, but IP addresses are not accepted for *hostname*.
415
CertificateError is raised on failure. On success, the function
419
raise ValueError("empty or no certificate")
421
san = cert.get('subjectAltName', ())
422
for key, value in san:
424
if _dnsname_to_pat(value).match(hostname):
426
dnsnames.append(value)
428
# The subject is only checked when subjectAltName is empty
429
for sub in cert.get('subject', ()):
430
for key, value in sub:
431
# XXX according to RFC 2818, the most specific Common Name
433
if key == 'commonName':
434
if _dnsname_to_pat(value).match(hostname):
436
dnsnames.append(value)
437
if len(dnsnames) > 1:
438
raise errors.CertificateError(
439
"hostname %r doesn't match either of %s"
440
% (hostname, ', '.join(map(repr, dnsnames))))
441
elif len(dnsnames) == 1:
442
raise errors.CertificateError("hostname %r doesn't match %r" %
443
(hostname, dnsnames[0]))
445
raise errors.CertificateError("no appropriate commonName or "
446
"subjectAltName fields were found")
328
449
class HTTPSConnection(AbstractHTTPConnection, httplib.HTTPSConnection):
330
451
def __init__(self, host, port=None, key_file=None, cert_file=None,
331
452
proxied_host=None,
332
report_activity=None):
453
report_activity=None, ca_certs=None):
333
454
AbstractHTTPConnection.__init__(self, report_activity=report_activity)
334
455
# Use strict=True since we don't support HTTP/0.9
335
456
httplib.HTTPSConnection.__init__(self, host, port,
336
457
key_file, cert_file, strict=True)
337
458
self.proxied_host = proxied_host
459
self.ca_certs = ca_certs
339
461
def connect(self):
340
462
if 'http' in debug.debug_flags:
345
467
self.connect_to_origin()
347
469
def connect_to_origin(self):
348
ssl_sock = _ssl_wrap_socket(self.sock, self.key_file, self.cert_file)
470
# FIXME JRV 2011-12-18: Use location config here?
471
config_stack = config.GlobalStack()
472
cert_reqs = config_stack.get('ssl.cert_reqs')
473
if cert_reqs == ssl.CERT_NONE:
474
trace.warning("Not checking SSL certificate for %s: %d",
475
self.host, self.port)
478
if self.ca_certs is None:
479
ca_certs = config_stack.get('ssl.ca_certs')
481
ca_certs = self.ca_certs
484
"No valid trusted SSL CA certificates file set. See "
485
"'bzr help ssl.ca_certs' for more information on setting "
488
ssl_sock = ssl.wrap_socket(self.sock, self.key_file, self.cert_file,
489
cert_reqs=cert_reqs, ca_certs=ca_certs)
490
except ssl.SSLError, e:
493
"See `bzr help ssl.ca_certs` for how to specify trusted CA"
495
"Pass -Ossl.cert_reqs=none to disable certificate "
496
"verification entirely.\n")
498
if cert_reqs == ssl.CERT_REQUIRED:
499
peer_cert = ssl_sock.getpeercert()
500
match_hostname(peer_cert, self.host)
349
502
# Wrap the ssl socket before anybody use it
350
503
self._wrap_socket_for_reporting(ssl_sock)
454
607
handler_order = 1000 # after all pre-processings
456
def __init__(self, report_activity=None):
609
def __init__(self, report_activity=None, ca_certs=None):
457
610
self._report_activity = report_activity
611
self.ca_certs = ca_certs
459
613
def create_connection(self, request, http_connection_class):
460
614
host = request.get_host()
469
623
connection = http_connection_class(
470
624
host, proxied_host=request.proxied_host,
471
report_activity=self._report_activity)
625
report_activity=self._report_activity,
626
ca_certs=self.ca_certs)
472
627
except httplib.InvalidURL, exception:
473
628
# There is only one occurrence of InvalidURL in httplib
474
629
raise errors.InvalidURL(request.get_full_url(),
660
815
% (request, request.connection.sock.getsockname())
661
816
response = connection.getresponse()
662
817
convert_to_addinfourl = True
818
except (ssl.SSLError, errors.CertificateError):
819
# Something is wrong with either the certificate or the hostname,
820
# re-trying won't help
663
822
except (socket.gaierror, httplib.BadStatusLine, httplib.UnknownProtocol,
664
823
socket.error, httplib.HTTPException):
665
824
response = self.retry_or_raise(http_class, request, first_try)
1657
1816
connection=ConnectionHandler,
1658
1817
redirect=HTTPRedirectHandler,
1659
1818
error=HTTPErrorProcessor,
1660
report_activity=None):
1819
report_activity=None,
1661
1821
self._opener = urllib2.build_opener(
1662
connection(report_activity=report_activity),
1822
connection(report_activity=report_activity, ca_certs=ca_certs),
1663
1823
redirect, error,
1664
1824
ProxyHandler(),
1665
1825
HTTPBasicAuthHandler(),