74
lazy_import.lazy_import(globals(), """
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
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
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)
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
133
opt_ssl_ca_certs = config.Option('ssl.ca_certs',
134
from_unicode=ca_certs_from_store,
135
default=default_ca_certs,
138
Path to certification authority certificates to trust.
140
This should be a valid path to a bundle containing all root Certificate
141
Authorities used to verify an https server certificate.
143
Use ssl.cert_reqs=none to disable certificate verification.
146
opt_ssl_cert_reqs = config.Option('ssl.cert_reqs',
147
default=default_ca_reqs,
148
from_unicode=cert_reqs_from_store,
151
Whether to require a certificate from the remote side. (default:required)
154
* none: Certificates ignored
155
* required: Certificates required and validated
158
checked_kerberos = False
162
class addinfourl(urllib2.addinfourl):
163
'''Replacement addinfourl class compatible with python-2.7's xmlrpclib
165
In python-2.7, xmlrpclib expects that the response object that it receives
166
has a getheader method. httplib.HTTPResponse provides this but
167
urllib2.addinfourl does not. Add the necessary functions here, ported to
168
use the internal data structures of addinfourl.
171
def getheader(self, name, default=None):
172
if self.headers is None:
173
raise httplib.ResponseNotReady()
174
return self.headers.getheader(name, default)
176
def getheaders(self):
177
if self.headers is None:
178
raise httplib.ResponseNotReady()
179
return self.headers.items()
78
182
class _ReportingFileSocket(object):
90
194
self.report_activity(len(s), 'read')
94
# This should be readline(self, size=-1), but httplib in python 2.4 and
95
# 2.5 defines a SSLFile wrapper whose readline method lacks the size
96
# parameter. So until we drop support for 2.4 and 2.5 and since we
97
# don't *need* the size parameter we'll stay with readline(self)
99
s = self.filesock.readline()
197
def readline(self, size=-1):
198
s = self.filesock.readline(size)
100
199
self.report_activity(len(s), 'read')
147
246
# Some responses have bodies in which we have no interest
148
_body_ignored_responses = [301,302, 303, 307, 401, 403, 404]
247
_body_ignored_responses = [301,302, 303, 307, 400, 401, 403, 404, 501]
150
249
# in finish() below, we may have to discard several MB in the worst
151
250
# case. To avoid buffering that much, we read and discard by chunks
246
345
def cleanup_pipe(self):
247
346
"""Read the remaining bytes of the last response if any."""
248
347
if self._response is not None:
249
pending = self._response.finish()
250
# Warn the user (once)
251
if (self._ranges_received_whole_file is None
252
and self._response.status == 200
253
and pending and pending > self._range_warning_thresold
255
self._ranges_received_whole_file = True
257
'Got a 200 response when asking for multiple ranges,'
258
' does your server at %s:%s support range requests?',
259
self.host, self.port)
349
pending = self._response.finish()
350
# Warn the user (once)
351
if (self._ranges_received_whole_file is None
352
and self._response.status == 200
353
and pending and pending > self._range_warning_thresold
355
self._ranges_received_whole_file = True
357
'Got a 200 response when asking for multiple ranges,'
358
' does your server at %s:%s support range requests?',
359
self.host, self.port)
360
except socket.error, e:
361
# It's conceivable that the socket is in a bad state here
362
# (including some test cases) and in this case, it doesn't need
363
# cleaning anymore, so no need to fail, we just get rid of the
364
# socket and let callers reconnect
366
or e.args[0] not in (errno.ECONNRESET, errno.ECONNABORTED)):
260
369
self._response = None
261
370
# Preserve our preciousss
276
385
# XXX: Needs refactoring at the caller level.
277
386
def __init__(self, host, port=None, proxied_host=None,
278
report_activity=None):
387
report_activity=None, ca_certs=None):
279
388
AbstractHTTPConnection.__init__(self, report_activity=report_activity)
280
389
# Use strict=True since we don't support HTTP/0.9
281
390
httplib.HTTPConnection.__init__(self, host, port, strict=True)
282
391
self.proxied_host = proxied_host
392
# ca_certs is ignored, it's only relevant for https
284
394
def connect(self):
285
395
if 'http' in debug.debug_flags:
288
398
self._wrap_socket_for_reporting(self.sock)
291
# Build the appropriate socket wrapper for ssl
293
# python 2.6 introduced a better ssl package
295
_ssl_wrap_socket = ssl.wrap_socket
297
# python versions prior to 2.6 don't have ssl and ssl.wrap_socket instead
298
# they use httplib.FakeSocket
299
def _ssl_wrap_socket(sock, key_file, cert_file):
300
ssl_sock = socket.ssl(sock, key_file, cert_file)
301
return httplib.FakeSocket(sock, ssl_sock)
401
# These two methods were imported from Python 3.2's ssl module
403
def _dnsname_to_pat(dn, max_wildcards=1):
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
412
"too many wildcards in certificate DNS name: " + repr(dn))
414
# When '*' is a fragment by itself, it matches a non-empty dotless
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)
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*.
429
CertificateError is raised on failure. On success, the function
433
raise ValueError("empty or no certificate")
435
san = cert.get('subjectAltName', ())
436
for key, value in san:
438
if _dnsname_to_pat(value).match(hostname):
440
dnsnames.append(value)
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
447
if key == 'commonName':
448
if _dnsname_to_pat(value).match(hostname):
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]))
459
raise errors.CertificateError("no appropriate commonName or "
460
"subjectAltName fields were found")
304
463
class HTTPSConnection(AbstractHTTPConnection, httplib.HTTPSConnection):
306
465
def __init__(self, host, port=None, key_file=None, cert_file=None,
307
466
proxied_host=None,
308
report_activity=None):
467
report_activity=None, ca_certs=None):
309
468
AbstractHTTPConnection.__init__(self, report_activity=report_activity)
310
469
# Use strict=True since we don't support HTTP/0.9
311
470
httplib.HTTPSConnection.__init__(self, host, port,
312
471
key_file, cert_file, strict=True)
313
472
self.proxied_host = proxied_host
473
self.ca_certs = ca_certs
315
475
def connect(self):
316
476
if 'http' in debug.debug_flags:
321
481
self.connect_to_origin()
323
483
def connect_to_origin(self):
324
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]
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')
496
if self.ca_certs is None:
497
ca_certs = config_stack.get('ssl.ca_certs')
499
ca_certs = self.ca_certs
502
"No valid trusted SSL CA certificates file set. See "
503
"'bzr help ssl.ca_certs' for more information on setting "
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:
511
"See `bzr help ssl.ca_certs` for how to specify trusted CA"
513
"Pass -Ossl.cert_reqs=none to disable certificate "
514
"verification entirely.\n")
516
if cert_reqs == ssl.CERT_REQUIRED:
517
peer_cert = ssl_sock.getpeercert()
518
match_hostname(peer_cert, host)
325
520
# Wrap the ssl socket before anybody use it
326
521
self._wrap_socket_for_reporting(ssl_sock)
622
821
if 'http' in debug.debug_flags:
623
822
trace.mutter('> %s %s' % (method, url))
624
hdrs = ['%s: %s' % (k, v) for k,v in headers.items()]
824
for k,v in headers.iteritems():
825
# People are often told to paste -Dhttp output to help
826
# debug. Don't compromise credentials.
827
if k in ('Authorization', 'Proxy-Authorization'):
829
hdrs.append('%s: %s' % (k, v))
625
830
trace.mutter('> ' + '\n> '.join(hdrs) + '\n')
626
831
if self._debuglevel >= 1:
627
832
print 'Request sent: [%r] from (%s)' \
628
833
% (request, request.connection.sock.getsockname())
629
834
response = connection.getresponse()
630
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
631
840
except (socket.gaierror, httplib.BadStatusLine, httplib.UnknownProtocol,
632
841
socket.error, httplib.HTTPException):
633
842
response = self.retry_or_raise(http_class, request, first_try)
908
1117
def proxy_bypass(self, host):
909
"""Check if host should be proxied or not"""
1118
"""Check if host should be proxied or not.
1120
:returns: True to skip the proxy, False otherwise.
910
1122
no_proxy = self.get_proxy_env_var('no', default_to=None)
1123
bypass = self.evaluate_proxy_bypass(host, no_proxy)
1125
# Nevertheless, there are platform-specific ways to
1127
return urllib.proxy_bypass(host)
1131
def evaluate_proxy_bypass(self, host, no_proxy):
1132
"""Check the host against a comma-separated no_proxy list as a string.
1134
:param host: ``host:port`` being requested
1136
:param no_proxy: comma-separated list of hosts to access directly.
1138
:returns: True to skip the proxy, False not to, or None to
911
1141
if no_proxy is None:
1142
# All hosts are proxied
913
1144
hhost, hport = urllib.splitport(host)
914
1145
# Does host match any of the domains mentioned in
949
1181
# proxied request, intialize. scheme (the authentication scheme)
950
1182
# and realm will be set by the AuthHandler
951
1183
request.proxy_auth = {
952
'host': host, 'port': port,
953
'user': user, 'password': password,
1184
'host': parsed_url.host,
1185
'port': parsed_url.port,
1186
'user': parsed_url.user,
1187
'password': parsed_url.password,
1188
'protocol': parsed_url.scheme,
955
1189
# We ignore path since we connect to a proxy
1191
if parsed_url.port is None:
1192
phost = parsed_url.host
960
phost = host + ':%d' % port
1194
phost = parsed_url.host + ':%d' % parsed_url.port
961
1195
request.set_proxy(phost, type)
962
1196
if self._debuglevel >= 3:
963
1197
print 'set_proxy: proxy set to %s://%s' % (type, phost)
1072
1306
auth['modified'] = False
1073
1307
# Put some common info in auth if the caller didn't
1074
1308
if auth.get('path', None) is None:
1076
host, port, path) = urlutils.parse_url(request.get_full_url())
1077
self.update_auth(auth, 'protocol', protocol)
1078
self.update_auth(auth, 'host', host)
1079
self.update_auth(auth, 'port', port)
1080
self.update_auth(auth, 'path', path)
1309
parsed_url = urlutils.URL.from_string(request.get_full_url())
1310
self.update_auth(auth, 'protocol', parsed_url.scheme)
1311
self.update_auth(auth, 'host', parsed_url.host)
1312
self.update_auth(auth, 'port', parsed_url.port)
1313
self.update_auth(auth, 'path', parsed_url.path)
1081
1314
# FIXME: the auth handler should be selected at a single place instead
1082
1315
# of letting all handlers try to match all headers, but the current
1083
1316
# design doesn't allow a simple implementation.
1182
1415
user = auth.get('user', None)
1183
1416
password = auth.get('password', None)
1184
1417
realm = auth['realm']
1418
port = auth.get('port', None)
1186
1420
if user is None:
1187
1421
user = auth_conf.get_user(auth['protocol'], auth['host'],
1188
port=auth['port'], path=auth['path'],
1422
port=port, path=auth['path'],
1189
1423
realm=realm, ask=True,
1190
1424
prompt=self.build_username_prompt(auth))
1191
1425
if user is not None and password is None:
1192
1426
password = auth_conf.get_password(
1193
auth['protocol'], auth['host'], user, port=auth['port'],
1427
auth['protocol'], auth['host'], user,
1194
1429
path=auth['path'], realm=realm,
1195
1430
prompt=self.build_password_prompt(auth))
1500
1742
def build_password_prompt(self, auth):
1501
1743
prompt = self._build_password_prompt(auth)
1502
prompt = 'Proxy ' + prompt
1744
prompt = u'Proxy ' + prompt
1505
1747
def build_username_prompt(self, auth):
1506
1748
prompt = self._build_username_prompt(auth)
1507
prompt = 'Proxy ' + prompt
1749
prompt = u'Proxy ' + prompt
1510
1752
def http_error_407(self, req, fp, code, msg, headers):