##// END OF EJS Templates
url: raise error if CONNECT request to proxy was unsuccessful...
Manuel Jacob -
r50172:51b07ac1 stable
parent child Browse files
Show More
@@ -1,739 +1,667
1 1 # url.py - HTTP handling for mercurial
2 2 #
3 3 # Copyright 2005, 2006, 2007, 2008 Olivia Mackall <olivia@selenic.com>
4 4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
5 5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9
10 10 from __future__ import absolute_import
11 11
12 12 import base64
13 13 import socket
14 14 import sys
15 15
16 16 from .i18n import _
17 from .pycompat import getattr
18 17 from . import (
19 18 encoding,
20 19 error,
21 20 httpconnection as httpconnectionmod,
22 21 keepalive,
23 22 pycompat,
24 23 sslutil,
25 24 urllibcompat,
26 25 util,
27 26 )
28 27 from .utils import (
29 28 stringutil,
30 29 urlutil,
31 30 )
32 31
33 32 httplib = util.httplib
34 33 stringio = util.stringio
35 34 urlerr = util.urlerr
36 35 urlreq = util.urlreq
37 36
38 37
39 38 def escape(s, quote=None):
40 39 """Replace special characters "&", "<" and ">" to HTML-safe sequences.
41 40 If the optional flag quote is true, the quotation mark character (")
42 41 is also translated.
43 42
44 43 This is the same as cgi.escape in Python, but always operates on
45 44 bytes, whereas cgi.escape in Python 3 only works on unicodes.
46 45 """
47 46 s = s.replace(b"&", b"&amp;")
48 47 s = s.replace(b"<", b"&lt;")
49 48 s = s.replace(b">", b"&gt;")
50 49 if quote:
51 50 s = s.replace(b'"', b"&quot;")
52 51 return s
53 52
54 53
55 54 class passwordmgr(object):
56 55 def __init__(self, ui, passwddb):
57 56 self.ui = ui
58 57 self.passwddb = passwddb
59 58
60 59 def add_password(self, realm, uri, user, passwd):
61 60 return self.passwddb.add_password(realm, uri, user, passwd)
62 61
63 62 def find_user_password(self, realm, authuri):
64 63 assert isinstance(realm, (type(None), str))
65 64 assert isinstance(authuri, str)
66 65 authinfo = self.passwddb.find_user_password(realm, authuri)
67 66 user, passwd = authinfo
68 67 user, passwd = pycompat.bytesurl(user), pycompat.bytesurl(passwd)
69 68 if user and passwd:
70 69 self._writedebug(user, passwd)
71 70 return (pycompat.strurl(user), pycompat.strurl(passwd))
72 71
73 72 if not user or not passwd:
74 73 res = httpconnectionmod.readauthforuri(self.ui, authuri, user)
75 74 if res:
76 75 group, auth = res
77 76 user, passwd = auth.get(b'username'), auth.get(b'password')
78 77 self.ui.debug(b"using auth.%s.* for authentication\n" % group)
79 78 if not user or not passwd:
80 79 u = urlutil.url(pycompat.bytesurl(authuri))
81 80 u.query = None
82 81 if not self.ui.interactive():
83 82 raise error.Abort(
84 83 _(b'http authorization required for %s')
85 84 % urlutil.hidepassword(bytes(u))
86 85 )
87 86
88 87 self.ui.write(
89 88 _(b"http authorization required for %s\n")
90 89 % urlutil.hidepassword(bytes(u))
91 90 )
92 91 self.ui.write(_(b"realm: %s\n") % pycompat.bytesurl(realm))
93 92 if user:
94 93 self.ui.write(_(b"user: %s\n") % user)
95 94 else:
96 95 user = self.ui.prompt(_(b"user:"), default=None)
97 96
98 97 if not passwd:
99 98 passwd = self.ui.getpass()
100 99
101 100 # As of Python 3.8, the default implementation of
102 101 # AbstractBasicAuthHandler.retry_http_basic_auth() assumes the user
103 102 # is set if pw is not None. This means (None, str) is not a valid
104 103 # return type of find_user_password().
105 104 if user is None:
106 105 return None, None
107 106
108 107 self.passwddb.add_password(realm, authuri, user, passwd)
109 108 self._writedebug(user, passwd)
110 109 return (pycompat.strurl(user), pycompat.strurl(passwd))
111 110
112 111 def _writedebug(self, user, passwd):
113 112 msg = _(b'http auth: user %s, password %s\n')
114 113 self.ui.debug(msg % (user, passwd and b'*' * len(passwd) or b'not set'))
115 114
116 115 def find_stored_password(self, authuri):
117 116 return self.passwddb.find_user_password(None, authuri)
118 117
119 118
120 119 class proxyhandler(urlreq.proxyhandler):
121 120 def __init__(self, ui):
122 121 proxyurl = ui.config(b"http_proxy", b"host") or encoding.environ.get(
123 122 b'http_proxy'
124 123 )
125 124 # XXX proxyauthinfo = None
126 125
127 126 if proxyurl:
128 127 # proxy can be proper url or host[:port]
129 128 if not (
130 129 proxyurl.startswith(b'http:') or proxyurl.startswith(b'https:')
131 130 ):
132 131 proxyurl = b'http://' + proxyurl + b'/'
133 132 proxy = urlutil.url(proxyurl)
134 133 if not proxy.user:
135 134 proxy.user = ui.config(b"http_proxy", b"user")
136 135 proxy.passwd = ui.config(b"http_proxy", b"passwd")
137 136
138 137 # see if we should use a proxy for this url
139 138 no_list = [b"localhost", b"127.0.0.1"]
140 139 no_list.extend(
141 140 [p.lower() for p in ui.configlist(b"http_proxy", b"no")]
142 141 )
143 142 no_list.extend(
144 143 [
145 144 p.strip().lower()
146 145 for p in encoding.environ.get(b"no_proxy", b'').split(b',')
147 146 if p.strip()
148 147 ]
149 148 )
150 149 # "http_proxy.always" config is for running tests on localhost
151 150 if ui.configbool(b"http_proxy", b"always"):
152 151 self.no_list = []
153 152 else:
154 153 self.no_list = no_list
155 154
156 155 # Keys and values need to be str because the standard library
157 156 # expects them to be.
158 157 proxyurl = str(proxy)
159 158 proxies = {'http': proxyurl, 'https': proxyurl}
160 159 ui.debug(
161 160 b'proxying through %s\n' % urlutil.hidepassword(bytes(proxy))
162 161 )
163 162 else:
164 163 proxies = {}
165 164
166 165 urlreq.proxyhandler.__init__(self, proxies)
167 166 self.ui = ui
168 167
169 168 def proxy_open(self, req, proxy, type_):
170 169 host = pycompat.bytesurl(urllibcompat.gethost(req)).split(b':')[0]
171 170 for e in self.no_list:
172 171 if host == e:
173 172 return None
174 173 if e.startswith(b'*.') and host.endswith(e[2:]):
175 174 return None
176 175 if e.startswith(b'.') and host.endswith(e[1:]):
177 176 return None
178 177
179 178 return urlreq.proxyhandler.proxy_open(self, req, proxy, type_)
180 179
181 180
182 181 def _gen_sendfile(orgsend):
183 182 def _sendfile(self, data):
184 183 # send a file
185 184 if isinstance(data, httpconnectionmod.httpsendfile):
186 185 # if auth required, some data sent twice, so rewind here
187 186 data.seek(0)
188 187 for chunk in util.filechunkiter(data):
189 188 orgsend(self, chunk)
190 189 else:
191 190 orgsend(self, data)
192 191
193 192 return _sendfile
194 193
195 194
196 195 has_https = util.safehasattr(urlreq, b'httpshandler')
197 196
198 197
199 198 class httpconnection(keepalive.HTTPConnection):
200 199 # must be able to send big bundle as stream.
201 200 send = _gen_sendfile(keepalive.HTTPConnection.send)
202 201
203 def getresponse(self):
204 proxyres = getattr(self, 'proxyres', None)
205 if proxyres:
206 if proxyres.will_close:
207 self.close()
208 self.proxyres = None
209 return proxyres
210 return keepalive.HTTPConnection.getresponse(self)
211
212 202
213 203 # Large parts of this function have their origin from before Python 2.6
214 204 # and could potentially be removed.
215 205 def _generic_start_transaction(handler, h, req):
216 206 tunnel_host = req._tunnel_host
217 207 if tunnel_host:
218 208 if tunnel_host[:7] not in ['http://', 'https:/']:
219 209 tunnel_host = 'https://' + tunnel_host
220 210 new_tunnel = True
221 211 else:
222 212 tunnel_host = urllibcompat.getselector(req)
223 213 new_tunnel = False
224 214
225 215 if new_tunnel or tunnel_host == urllibcompat.getfullurl(req): # has proxy
226 216 u = urlutil.url(pycompat.bytesurl(tunnel_host))
227 217 if new_tunnel or u.scheme == b'https': # only use CONNECT for HTTPS
228 218 h.realhostport = b':'.join([u.host, (u.port or b'443')])
229 219 h.headers = req.headers.copy()
230 220 h.headers.update(handler.parent.addheaders)
231 221 return
232 222
233 223 h.realhostport = None
234 224 h.headers = None
235 225
236 226
237 227 def _generic_proxytunnel(self):
238 228 proxyheaders = {
239 229 pycompat.bytestr(x): pycompat.bytestr(self.headers[x])
240 230 for x in self.headers
241 231 if x.lower().startswith('proxy-')
242 232 }
243 233 self.send(b'CONNECT %s HTTP/1.0\r\n' % self.realhostport)
244 234 for header in pycompat.iteritems(proxyheaders):
245 235 self.send(b'%s: %s\r\n' % header)
246 236 self.send(b'\r\n')
247 237
248 238 # majority of the following code is duplicated from
249 239 # httplib.HTTPConnection as there are no adequate places to
250 240 # override functions to provide the needed functionality
251 241 # strict was removed in Python 3.4.
252 242 kwargs = {}
253 243 if not pycompat.ispy3:
254 244 kwargs[b'strict'] = self.strict
255 245
256 246 res = self.response_class(self.sock, method=self._method, **kwargs)
257 247
258 248 while True:
259 249 version, status, reason = res._read_status()
260 250 if status != httplib.CONTINUE:
261 251 break
262 252 # skip lines that are all whitespace
263 253 list(iter(lambda: res.fp.readline().strip(), b''))
264 res.status = status
265 res.reason = reason.strip()
266 254
267 if res.status == 200:
255 if status == 200:
268 256 # skip lines until we find a blank line
269 257 list(iter(res.fp.readline, b'\r\n'))
270 return True
271
272 if version == b'HTTP/1.0':
273 res.version = 10
274 elif version.startswith(b'HTTP/1.'):
275 res.version = 11
276 elif version == b'HTTP/0.9':
277 res.version = 9
278 258 else:
279 raise httplib.UnknownProtocol(version)
280
281 if res.version == 9:
282 res.length = None
283 res.chunked = 0
284 res.will_close = 1
285 res.msg = httplib.HTTPMessage(stringio())
286 return False
287
288 res.msg = httplib.HTTPMessage(res.fp)
289 res.msg.fp = None
290
291 # are we using the chunked-style of transfer encoding?
292 trenc = res.msg.getheader(b'transfer-encoding')
293 if trenc and trenc.lower() == b"chunked":
294 res.chunked = 1
295 res.chunk_left = None
296 else:
297 res.chunked = 0
298
299 # will the connection close at the end of the response?
300 res.will_close = res._check_close()
301
302 # do we have a Content-Length?
303 # NOTE: RFC 2616, section 4.4, #3 says we ignore this if
304 # transfer-encoding is "chunked"
305 length = res.msg.getheader(b'content-length')
306 if length and not res.chunked:
307 try:
308 res.length = int(length)
309 except ValueError:
310 res.length = None
311 else:
312 if res.length < 0: # ignore nonsensical negative lengths
313 res.length = None
314 else:
315 res.length = None
316
317 # does the body have a fixed length? (of zero)
318 if (
319 status == httplib.NO_CONTENT
320 or status == httplib.NOT_MODIFIED
321 or 100 <= status < 200
322 or res._method == b'HEAD' # 1xx codes
323 ):
324 res.length = 0
325
326 # if the connection remains open, and we aren't using chunked, and
327 # a content-length was not provided, then assume that the connection
328 # WILL close.
329 if not res.will_close and not res.chunked and res.length is None:
330 res.will_close = 1
331
332 self.proxyres = res
333
334 return False
259 self.close()
260 raise socket.error(
261 "Tunnel connection failed: %d %s" % (status, reason.strip())
262 )
335 263
336 264
337 265 class httphandler(keepalive.HTTPHandler):
338 266 def http_open(self, req):
339 267 return self.do_open(httpconnection, req)
340 268
341 269 def _start_transaction(self, h, req):
342 270 _generic_start_transaction(self, h, req)
343 271 return keepalive.HTTPHandler._start_transaction(self, h, req)
344 272
345 273
346 274 class logginghttpconnection(keepalive.HTTPConnection):
347 275 def __init__(self, createconn, *args, **kwargs):
348 276 keepalive.HTTPConnection.__init__(self, *args, **kwargs)
349 277 self._create_connection = createconn
350 278
351 279 if sys.version_info < (2, 7, 7):
352 280 # copied from 2.7.14, since old implementations directly call
353 281 # socket.create_connection()
354 282 def connect(self):
355 283 self.sock = self._create_connection(
356 284 (self.host, self.port), self.timeout, self.source_address
357 285 )
358 286 if self._tunnel_host:
359 287 self._tunnel()
360 288
361 289
362 290 class logginghttphandler(httphandler):
363 291 """HTTP handler that logs socket I/O."""
364 292
365 293 def __init__(self, logfh, name, observeropts, timeout=None):
366 294 super(logginghttphandler, self).__init__(timeout=timeout)
367 295
368 296 self._logfh = logfh
369 297 self._logname = name
370 298 self._observeropts = observeropts
371 299
372 300 # do_open() calls the passed class to instantiate an HTTPConnection. We
373 301 # pass in a callable method that creates a custom HTTPConnection instance
374 302 # whose callback to create the socket knows how to proxy the socket.
375 303 def http_open(self, req):
376 304 return self.do_open(self._makeconnection, req)
377 305
378 306 def _makeconnection(self, *args, **kwargs):
379 307 def createconnection(*args, **kwargs):
380 308 sock = socket.create_connection(*args, **kwargs)
381 309 return util.makeloggingsocket(
382 310 self._logfh, sock, self._logname, **self._observeropts
383 311 )
384 312
385 313 return logginghttpconnection(createconnection, *args, **kwargs)
386 314
387 315
388 316 if has_https:
389 317
390 318 class httpsconnection(keepalive.HTTPConnection):
391 319 response_class = keepalive.HTTPResponse
392 320 default_port = httplib.HTTPS_PORT
393 321 # must be able to send big bundle as stream.
394 322 send = _gen_sendfile(keepalive.safesend)
395 323 getresponse = keepalive.wrapgetresponse(httplib.HTTPConnection)
396 324
397 325 def __init__(
398 326 self,
399 327 host,
400 328 port=None,
401 329 key_file=None,
402 330 cert_file=None,
403 331 *args,
404 332 **kwargs
405 333 ):
406 334 keepalive.HTTPConnection.__init__(self, host, port, *args, **kwargs)
407 335 self.key_file = key_file
408 336 self.cert_file = cert_file
409 337
410 338 def connect(self):
411 339 self.sock = socket.create_connection((self.host, self.port))
412 340
413 341 host = self.host
414 342 if self.realhostport: # use CONNECT proxy
415 343 _generic_proxytunnel(self)
416 344 host = self.realhostport.rsplit(b':', 1)[0]
417 345 self.sock = sslutil.wrapsocket(
418 346 self.sock,
419 347 self.key_file,
420 348 self.cert_file,
421 349 ui=self.ui,
422 350 serverhostname=host,
423 351 )
424 352 sslutil.validatesocket(self.sock)
425 353
426 354 class httpshandler(keepalive.KeepAliveHandler, urlreq.httpshandler):
427 355 def __init__(self, ui, timeout=None):
428 356 keepalive.KeepAliveHandler.__init__(self, timeout=timeout)
429 357 urlreq.httpshandler.__init__(self)
430 358 self.ui = ui
431 359 self.pwmgr = passwordmgr(self.ui, self.ui.httppasswordmgrdb)
432 360
433 361 def _start_transaction(self, h, req):
434 362 _generic_start_transaction(self, h, req)
435 363 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
436 364
437 365 def https_open(self, req):
438 366 # urllibcompat.getfullurl() does not contain credentials
439 367 # and we may need them to match the certificates.
440 368 url = urllibcompat.getfullurl(req)
441 369 user, password = self.pwmgr.find_stored_password(url)
442 370 res = httpconnectionmod.readauthforuri(self.ui, url, user)
443 371 if res:
444 372 group, auth = res
445 373 self.auth = auth
446 374 self.ui.debug(b"using auth.%s.* for authentication\n" % group)
447 375 else:
448 376 self.auth = None
449 377 return self.do_open(self._makeconnection, req)
450 378
451 379 def _makeconnection(self, host, port=None, *args, **kwargs):
452 380 keyfile = None
453 381 certfile = None
454 382
455 383 if len(args) >= 1: # key_file
456 384 keyfile = args[0]
457 385 if len(args) >= 2: # cert_file
458 386 certfile = args[1]
459 387 args = args[2:]
460 388
461 389 # if the user has specified different key/cert files in
462 390 # hgrc, we prefer these
463 391 if self.auth and b'key' in self.auth and b'cert' in self.auth:
464 392 keyfile = self.auth[b'key']
465 393 certfile = self.auth[b'cert']
466 394
467 395 conn = httpsconnection(
468 396 host, port, keyfile, certfile, *args, **kwargs
469 397 )
470 398 conn.ui = self.ui
471 399 return conn
472 400
473 401
474 402 class httpdigestauthhandler(urlreq.httpdigestauthhandler):
475 403 def __init__(self, *args, **kwargs):
476 404 urlreq.httpdigestauthhandler.__init__(self, *args, **kwargs)
477 405 self.retried_req = None
478 406
479 407 def reset_retry_count(self):
480 408 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
481 409 # forever. We disable reset_retry_count completely and reset in
482 410 # http_error_auth_reqed instead.
483 411 pass
484 412
485 413 def http_error_auth_reqed(self, auth_header, host, req, headers):
486 414 # Reset the retry counter once for each request.
487 415 if req is not self.retried_req:
488 416 self.retried_req = req
489 417 self.retried = 0
490 418 return urlreq.httpdigestauthhandler.http_error_auth_reqed(
491 419 self, auth_header, host, req, headers
492 420 )
493 421
494 422
495 423 class httpbasicauthhandler(urlreq.httpbasicauthhandler):
496 424 def __init__(self, *args, **kwargs):
497 425 self.auth = None
498 426 urlreq.httpbasicauthhandler.__init__(self, *args, **kwargs)
499 427 self.retried_req = None
500 428
501 429 def http_request(self, request):
502 430 if self.auth:
503 431 request.add_unredirected_header(self.auth_header, self.auth)
504 432
505 433 return request
506 434
507 435 def https_request(self, request):
508 436 if self.auth:
509 437 request.add_unredirected_header(self.auth_header, self.auth)
510 438
511 439 return request
512 440
513 441 def reset_retry_count(self):
514 442 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
515 443 # forever. We disable reset_retry_count completely and reset in
516 444 # http_error_auth_reqed instead.
517 445 pass
518 446
519 447 def http_error_auth_reqed(self, auth_header, host, req, headers):
520 448 # Reset the retry counter once for each request.
521 449 if req is not self.retried_req:
522 450 self.retried_req = req
523 451 self.retried = 0
524 452 return urlreq.httpbasicauthhandler.http_error_auth_reqed(
525 453 self, auth_header, host, req, headers
526 454 )
527 455
528 456 def retry_http_basic_auth(self, host, req, realm):
529 457 user, pw = self.passwd.find_user_password(
530 458 realm, urllibcompat.getfullurl(req)
531 459 )
532 460 if pw is not None:
533 461 raw = b"%s:%s" % (pycompat.bytesurl(user), pycompat.bytesurl(pw))
534 462 auth = 'Basic %s' % pycompat.strurl(base64.b64encode(raw).strip())
535 463 if req.get_header(self.auth_header, None) == auth:
536 464 return None
537 465 self.auth = auth
538 466 req.add_unredirected_header(self.auth_header, auth)
539 467 return self.parent.open(req)
540 468 else:
541 469 return None
542 470
543 471
544 472 class cookiehandler(urlreq.basehandler):
545 473 def __init__(self, ui):
546 474 self.cookiejar = None
547 475
548 476 cookiefile = ui.config(b'auth', b'cookiefile')
549 477 if not cookiefile:
550 478 return
551 479
552 480 cookiefile = util.expandpath(cookiefile)
553 481 try:
554 482 cookiejar = util.cookielib.MozillaCookieJar(
555 483 pycompat.fsdecode(cookiefile)
556 484 )
557 485 cookiejar.load()
558 486 self.cookiejar = cookiejar
559 487 except util.cookielib.LoadError as e:
560 488 ui.warn(
561 489 _(
562 490 b'(error loading cookie file %s: %s; continuing without '
563 491 b'cookies)\n'
564 492 )
565 493 % (cookiefile, stringutil.forcebytestr(e))
566 494 )
567 495
568 496 def http_request(self, request):
569 497 if self.cookiejar:
570 498 self.cookiejar.add_cookie_header(request)
571 499
572 500 return request
573 501
574 502 def https_request(self, request):
575 503 if self.cookiejar:
576 504 self.cookiejar.add_cookie_header(request)
577 505
578 506 return request
579 507
580 508
581 509 handlerfuncs = []
582 510
583 511
584 512 def opener(
585 513 ui,
586 514 authinfo=None,
587 515 useragent=None,
588 516 loggingfh=None,
589 517 loggingname=b's',
590 518 loggingopts=None,
591 519 sendaccept=True,
592 520 ):
593 521 """
594 522 construct an opener suitable for urllib2
595 523 authinfo will be added to the password manager
596 524
597 525 The opener can be configured to log socket events if the various
598 526 ``logging*`` arguments are specified.
599 527
600 528 ``loggingfh`` denotes a file object to log events to.
601 529 ``loggingname`` denotes the name of the to print when logging.
602 530 ``loggingopts`` is a dict of keyword arguments to pass to the constructed
603 531 ``util.socketobserver`` instance.
604 532
605 533 ``sendaccept`` allows controlling whether the ``Accept`` request header
606 534 is sent. The header is sent by default.
607 535 """
608 536 timeout = ui.configwith(float, b'http', b'timeout')
609 537 handlers = []
610 538
611 539 if loggingfh:
612 540 handlers.append(
613 541 logginghttphandler(
614 542 loggingfh, loggingname, loggingopts or {}, timeout=timeout
615 543 )
616 544 )
617 545 # We don't yet support HTTPS when logging I/O. If we attempt to open
618 546 # an HTTPS URL, we'll likely fail due to unknown protocol.
619 547
620 548 else:
621 549 handlers.append(httphandler(timeout=timeout))
622 550 if has_https:
623 551 handlers.append(httpshandler(ui, timeout=timeout))
624 552
625 553 handlers.append(proxyhandler(ui))
626 554
627 555 passmgr = passwordmgr(ui, ui.httppasswordmgrdb)
628 556 if authinfo is not None:
629 557 realm, uris, user, passwd = authinfo
630 558 saveduser, savedpass = passmgr.find_stored_password(uris[0])
631 559 if user != saveduser or passwd:
632 560 passmgr.add_password(realm, uris, user, passwd)
633 561 ui.debug(
634 562 b'http auth: user %s, password %s\n'
635 563 % (user, passwd and b'*' * len(passwd) or b'not set')
636 564 )
637 565
638 566 handlers.extend(
639 567 (httpbasicauthhandler(passmgr), httpdigestauthhandler(passmgr))
640 568 )
641 569 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
642 570 handlers.append(cookiehandler(ui))
643 571 opener = urlreq.buildopener(*handlers)
644 572
645 573 # keepalive.py's handlers will populate these attributes if they exist.
646 574 opener.requestscount = 0
647 575 opener.sentbytescount = 0
648 576 opener.receivedbytescount = 0
649 577
650 578 # The user agent should should *NOT* be used by servers for e.g.
651 579 # protocol detection or feature negotiation: there are other
652 580 # facilities for that.
653 581 #
654 582 # "mercurial/proto-1.0" was the original user agent string and
655 583 # exists for backwards compatibility reasons.
656 584 #
657 585 # The "(Mercurial %s)" string contains the distribution
658 586 # name and version. Other client implementations should choose their
659 587 # own distribution name. Since servers should not be using the user
660 588 # agent string for anything, clients should be able to define whatever
661 589 # user agent they deem appropriate.
662 590 #
663 591 # The custom user agent is for lfs, because unfortunately some servers
664 592 # do look at this value.
665 593 if not useragent:
666 594 agent = b'mercurial/proto-1.0 (Mercurial %s)' % util.version()
667 595 opener.addheaders = [('User-agent', pycompat.sysstr(agent))]
668 596 else:
669 597 opener.addheaders = [('User-agent', pycompat.sysstr(useragent))]
670 598
671 599 # This header should only be needed by wire protocol requests. But it has
672 600 # been sent on all requests since forever. We keep sending it for backwards
673 601 # compatibility reasons. Modern versions of the wire protocol use
674 602 # X-HgProto-<N> for advertising client support.
675 603 if sendaccept:
676 604 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
677 605
678 606 return opener
679 607
680 608
681 609 def open(ui, url_, data=None, sendaccept=True):
682 610 u = urlutil.url(url_)
683 611 if u.scheme:
684 612 u.scheme = u.scheme.lower()
685 613 url_, authinfo = u.authinfo()
686 614 else:
687 615 path = util.normpath(util.abspath(url_))
688 616 url_ = b'file://' + pycompat.bytesurl(
689 617 urlreq.pathname2url(pycompat.fsdecode(path))
690 618 )
691 619 authinfo = None
692 620 return opener(ui, authinfo, sendaccept=sendaccept).open(
693 621 pycompat.strurl(url_), data
694 622 )
695 623
696 624
697 625 def wrapresponse(resp):
698 626 """Wrap a response object with common error handlers.
699 627
700 628 This ensures that any I/O from any consumer raises the appropriate
701 629 error and messaging.
702 630 """
703 631 origread = resp.read
704 632
705 633 class readerproxy(resp.__class__):
706 634 def read(self, size=None):
707 635 try:
708 636 return origread(size)
709 637 except httplib.IncompleteRead as e:
710 638 # e.expected is an integer if length known or None otherwise.
711 639 if e.expected:
712 640 got = len(e.partial)
713 641 total = e.expected + got
714 642 msg = _(
715 643 b'HTTP request error (incomplete response; '
716 644 b'expected %d bytes got %d)'
717 645 ) % (total, got)
718 646 else:
719 647 msg = _(b'HTTP request error (incomplete response)')
720 648
721 649 raise error.PeerTransportError(
722 650 msg,
723 651 hint=_(
724 652 b'this may be an intermittent network failure; '
725 653 b'if the error persists, consider contacting the '
726 654 b'network or server operator'
727 655 ),
728 656 )
729 657 except httplib.HTTPException as e:
730 658 raise error.PeerTransportError(
731 659 _(b'HTTP request error (%s)') % e,
732 660 hint=_(
733 661 b'this may be an intermittent network failure; '
734 662 b'if the error persists, consider contacting the '
735 663 b'network or server operator'
736 664 ),
737 665 )
738 666
739 667 resp.__class__ = readerproxy
@@ -1,554 +1,561
1 1 #require serve ssl
2 2
3 3 Proper https client requires the built-in ssl from Python 2.6.
4 4
5 5 Disable the system configuration which may set stricter TLS requirements.
6 6 This test expects that legacy TLS versions are supported.
7 7
8 8 $ OPENSSL_CONF=
9 9 $ export OPENSSL_CONF
10 10
11 11 Make server certificates:
12 12
13 13 $ CERTSDIR="$TESTDIR/sslcerts"
14 14 $ cat "$CERTSDIR/priv.pem" "$CERTSDIR/pub.pem" >> server.pem
15 15 $ PRIV=`pwd`/server.pem
16 16 $ cat "$CERTSDIR/priv.pem" "$CERTSDIR/pub-not-yet.pem" > server-not-yet.pem
17 17 $ cat "$CERTSDIR/priv.pem" "$CERTSDIR/pub-expired.pem" > server-expired.pem
18 18
19 19 $ hg init test
20 20 $ cd test
21 21 $ echo foo>foo
22 22 $ mkdir foo.d foo.d/bAr.hg.d foo.d/baR.d.hg
23 23 $ echo foo>foo.d/foo
24 24 $ echo bar>foo.d/bAr.hg.d/BaR
25 25 $ echo bar>foo.d/baR.d.hg/bAR
26 26 $ hg commit -A -m 1
27 27 adding foo
28 28 adding foo.d/bAr.hg.d/BaR
29 29 adding foo.d/baR.d.hg/bAR
30 30 adding foo.d/foo
31 31 $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV
32 32 $ cat ../hg0.pid >> $DAEMON_PIDS
33 33
34 34 cacert not found
35 35
36 36 $ hg in --config web.cacerts=no-such.pem https://localhost:$HGPORT/
37 37 abort: could not find web.cacerts: no-such.pem
38 38 [255]
39 39
40 40 Test server address cannot be reused
41 41
42 42 $ hg serve -p $HGPORT --certificate=$PRIV 2>&1
43 43 abort: cannot start server at 'localhost:$HGPORT': $EADDRINUSE$
44 44 [255]
45 45
46 46 $ cd ..
47 47
48 48 Our test cert is not signed by a trusted CA. It should fail to verify if
49 49 we are able to load CA certs.
50 50
51 51 #if no-defaultcacertsloaded
52 52 $ hg clone https://localhost:$HGPORT/ copy-pull
53 53 (an attempt was made to load CA certificates but none were loaded; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error)
54 54 abort: error: *certificate verify failed* (glob)
55 55 [100]
56 56 #endif
57 57
58 58 #if defaultcacertsloaded
59 59 $ hg clone https://localhost:$HGPORT/ copy-pull
60 60 (the full certificate chain may not be available locally; see "hg help debugssl") (windows !)
61 61 abort: error: *certificate verify failed* (glob)
62 62 [100]
63 63 #endif
64 64
65 65 Specifying a per-host certificate file that doesn't exist will abort. The full
66 66 C:/path/to/msysroot will print on Windows.
67 67
68 68 $ hg --config hostsecurity.localhost:verifycertsfile=/does/not/exist clone https://localhost:$HGPORT/
69 69 abort: path specified by hostsecurity.localhost:verifycertsfile does not exist: */does/not/exist (glob)
70 70 [255]
71 71
72 72 A malformed per-host certificate file will raise an error
73 73
74 74 $ echo baddata > badca.pem
75 75 $ hg --config hostsecurity.localhost:verifycertsfile=badca.pem clone https://localhost:$HGPORT/
76 76 abort: error loading CA file badca.pem: * (glob)
77 77 (file is empty or malformed?)
78 78 [255]
79 79
80 80 A per-host certificate mismatching the server will fail verification
81 81
82 82 (modern ssl is able to discern whether the loaded cert is a CA cert)
83 83 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/client-cert.pem" clone https://localhost:$HGPORT/
84 84 (an attempt was made to load CA certificates but none were loaded; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error)
85 85 (the full certificate chain may not be available locally; see "hg help debugssl") (windows !)
86 86 abort: error: *certificate verify failed* (glob)
87 87 [100]
88 88
89 89 A per-host certificate matching the server's cert will be accepted
90 90
91 91 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/pub.pem" clone -U https://localhost:$HGPORT/ perhostgood1
92 92 requesting all changes
93 93 adding changesets
94 94 adding manifests
95 95 adding file changes
96 96 added 1 changesets with 4 changes to 4 files
97 97 new changesets 8b6053c928fe
98 98
99 99 A per-host certificate with multiple certs and one matching will be accepted
100 100
101 101 $ cat "$CERTSDIR/client-cert.pem" "$CERTSDIR/pub.pem" > perhost.pem
102 102 $ hg --config hostsecurity.localhost:verifycertsfile=perhost.pem clone -U https://localhost:$HGPORT/ perhostgood2
103 103 requesting all changes
104 104 adding changesets
105 105 adding manifests
106 106 adding file changes
107 107 added 1 changesets with 4 changes to 4 files
108 108 new changesets 8b6053c928fe
109 109
110 110 Defining both per-host certificate and a fingerprint will print a warning
111 111
112 112 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/pub.pem" --config hostsecurity.localhost:fingerprints=sha1:ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03 clone -U https://localhost:$HGPORT/ caandfingerwarning
113 113 (hostsecurity.localhost:verifycertsfile ignored when host fingerprints defined; using host fingerprints for verification)
114 114 requesting all changes
115 115 adding changesets
116 116 adding manifests
117 117 adding file changes
118 118 added 1 changesets with 4 changes to 4 files
119 119 new changesets 8b6053c928fe
120 120
121 121 $ DISABLECACERTS="--config devel.disableloaddefaultcerts=true"
122 122
123 123 Inability to verify peer certificate will result in abort
124 124
125 125 $ hg clone https://localhost:$HGPORT/ copy-pull $DISABLECACERTS
126 126 abort: unable to verify security of localhost (no loaded CA certificates); refusing to connect
127 127 (see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error or set hostsecurity.localhost:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e to trust this server)
128 128 [150]
129 129
130 130 $ hg clone --insecure https://localhost:$HGPORT/ copy-pull
131 131 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
132 132 requesting all changes
133 133 adding changesets
134 134 adding manifests
135 135 adding file changes
136 136 added 1 changesets with 4 changes to 4 files
137 137 new changesets 8b6053c928fe
138 138 updating to branch default
139 139 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
140 140 $ hg verify -R copy-pull
141 141 checking changesets
142 142 checking manifests
143 143 crosschecking files in changesets and manifests
144 144 checking files
145 145 checked 1 changesets with 4 changes to 4 files
146 146 $ cd test
147 147 $ echo bar > bar
148 148 $ hg commit -A -d '1 0' -m 2
149 149 adding bar
150 150 $ cd ..
151 151
152 152 pull without cacert
153 153
154 154 $ cd copy-pull
155 155 $ cat >> .hg/hgrc <<EOF
156 156 > [hooks]
157 157 > changegroup = sh -c "printenv.py --line changegroup"
158 158 > EOF
159 159 $ hg pull $DISABLECACERTS
160 160 pulling from https://localhost:$HGPORT/
161 161 abort: unable to verify security of localhost (no loaded CA certificates); refusing to connect
162 162 (see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error or set hostsecurity.localhost:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e to trust this server)
163 163 [150]
164 164
165 165 $ hg pull --insecure
166 166 pulling from https://localhost:$HGPORT/
167 167 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
168 168 searching for changes
169 169 adding changesets
170 170 adding manifests
171 171 adding file changes
172 172 added 1 changesets with 1 changes to 1 files
173 173 new changesets 5fed3813f7f5
174 174 changegroup hook: HG_HOOKNAME=changegroup
175 175 HG_HOOKTYPE=changegroup
176 176 HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d
177 177 HG_NODE_LAST=5fed3813f7f5e1824344fdc9cf8f63bb662c292d
178 178 HG_SOURCE=pull
179 179 HG_TXNID=TXN:$ID$
180 180 HG_TXNNAME=pull
181 181 https://localhost:$HGPORT/
182 182 HG_URL=https://localhost:$HGPORT/
183 183
184 184 (run 'hg update' to get a working copy)
185 185 $ cd ..
186 186
187 187 cacert configured in local repo
188 188
189 189 $ cp copy-pull/.hg/hgrc copy-pull/.hg/hgrc.bu
190 190 $ echo "[web]" >> copy-pull/.hg/hgrc
191 191 $ echo "cacerts=$CERTSDIR/pub.pem" >> copy-pull/.hg/hgrc
192 192 $ hg -R copy-pull pull
193 193 pulling from https://localhost:$HGPORT/
194 194 searching for changes
195 195 no changes found
196 196 $ mv copy-pull/.hg/hgrc.bu copy-pull/.hg/hgrc
197 197
198 198 cacert configured globally, also testing expansion of environment
199 199 variables in the filename
200 200
201 201 $ echo "[web]" >> $HGRCPATH
202 202 $ echo 'cacerts=$P/pub.pem' >> $HGRCPATH
203 203 $ P="$CERTSDIR" hg -R copy-pull pull
204 204 pulling from https://localhost:$HGPORT/
205 205 searching for changes
206 206 no changes found
207 207 $ P="$CERTSDIR" hg -R copy-pull pull --insecure
208 208 pulling from https://localhost:$HGPORT/
209 209 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
210 210 searching for changes
211 211 no changes found
212 212
213 213 empty cacert file
214 214
215 215 $ touch emptycafile
216 216
217 217 $ hg --config web.cacerts=emptycafile -R copy-pull pull
218 218 pulling from https://localhost:$HGPORT/
219 219 abort: error loading CA file emptycafile: * (glob)
220 220 (file is empty or malformed?)
221 221 [255]
222 222
223 223 cacert mismatch
224 224
225 225 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub.pem" \
226 226 > https://$LOCALIP:$HGPORT/
227 227 pulling from https://*:$HGPORT/ (glob)
228 228 abort: $LOCALIP certificate error: certificate is for localhost (glob)
229 229 (set hostsecurity.$LOCALIP:certfingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e config setting or use --insecure to connect insecurely)
230 230 [150]
231 231 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub.pem" \
232 232 > https://$LOCALIP:$HGPORT/ --insecure
233 233 pulling from https://*:$HGPORT/ (glob)
234 234 warning: connection security to $LOCALIP is disabled per current settings; communication is susceptible to eavesdropping and tampering (glob)
235 235 searching for changes
236 236 no changes found
237 237 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-other.pem"
238 238 pulling from https://localhost:$HGPORT/
239 239 (the full certificate chain may not be available locally; see "hg help debugssl") (windows !)
240 240 abort: error: *certificate verify failed* (glob)
241 241 [100]
242 242 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-other.pem" \
243 243 > --insecure
244 244 pulling from https://localhost:$HGPORT/
245 245 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
246 246 searching for changes
247 247 no changes found
248 248
249 249 Test server cert which isn't valid yet
250 250
251 251 $ hg serve -R test -p $HGPORT1 -d --pid-file=hg1.pid --certificate=server-not-yet.pem
252 252 $ cat hg1.pid >> $DAEMON_PIDS
253 253 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-not-yet.pem" \
254 254 > https://localhost:$HGPORT1/
255 255 pulling from https://localhost:$HGPORT1/
256 256 (the full certificate chain may not be available locally; see "hg help debugssl") (windows !)
257 257 abort: error: *certificate verify failed* (glob)
258 258 [100]
259 259
260 260 Test server cert which no longer is valid
261 261
262 262 $ hg serve -R test -p $HGPORT2 -d --pid-file=hg2.pid --certificate=server-expired.pem
263 263 $ cat hg2.pid >> $DAEMON_PIDS
264 264 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-expired.pem" \
265 265 > https://localhost:$HGPORT2/
266 266 pulling from https://localhost:$HGPORT2/
267 267 (the full certificate chain may not be available locally; see "hg help debugssl") (windows !)
268 268 abort: error: *certificate verify failed* (glob)
269 269 [100]
270 270
271 271 Setting ciphers to an invalid value aborts
272 272 $ P="$CERTSDIR" hg --config hostsecurity.ciphers=invalid -R copy-pull id https://localhost:$HGPORT/
273 273 abort: could not set ciphers: No cipher can be selected.
274 274 (change cipher string (invalid) in config)
275 275 [255]
276 276
277 277 $ P="$CERTSDIR" hg --config hostsecurity.localhost:ciphers=invalid -R copy-pull id https://localhost:$HGPORT/
278 278 abort: could not set ciphers: No cipher can be selected.
279 279 (change cipher string (invalid) in config)
280 280 [255]
281 281
282 282 Changing the cipher string works
283 283
284 284 $ P="$CERTSDIR" hg --config hostsecurity.ciphers=HIGH -R copy-pull id https://localhost:$HGPORT/
285 285 5fed3813f7f5
286 286
287 287 Fingerprints
288 288
289 289 - works without cacerts (hostfingerprints)
290 290 $ hg -R copy-pull id https://localhost:$HGPORT/ --insecure --config hostfingerprints.localhost=ec:d8:7c:d6:b3:86:d0:4f:c1:b8:b4:1c:9d:8f:5e:16:8e:ef:1c:03
291 291 (SHA-1 fingerprint for localhost found in legacy [hostfingerprints] section; if you trust this fingerprint, remove the old SHA-1 fingerprint from [hostfingerprints] and add the following entry to the new [hostsecurity] section: localhost:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e)
292 292 5fed3813f7f5
293 293
294 294 - works without cacerts (hostsecurity)
295 295 $ hg -R copy-pull id https://localhost:$HGPORT/ --config hostsecurity.localhost:fingerprints=sha1:ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03
296 296 5fed3813f7f5
297 297
298 298 $ hg -R copy-pull id https://localhost:$HGPORT/ --config hostsecurity.localhost:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e
299 299 5fed3813f7f5
300 300
301 301 - multiple fingerprints specified and first matches
302 302 $ hg --config 'hostfingerprints.localhost=ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03, deadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/ --insecure
303 303 (SHA-1 fingerprint for localhost found in legacy [hostfingerprints] section; if you trust this fingerprint, remove the old SHA-1 fingerprint from [hostfingerprints] and add the following entry to the new [hostsecurity] section: localhost:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e)
304 304 5fed3813f7f5
305 305
306 306 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03, sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/
307 307 5fed3813f7f5
308 308
309 309 - multiple fingerprints specified and last matches
310 310 $ hg --config 'hostfingerprints.localhost=deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03' -R copy-pull id https://localhost:$HGPORT/ --insecure
311 311 (SHA-1 fingerprint for localhost found in legacy [hostfingerprints] section; if you trust this fingerprint, remove the old SHA-1 fingerprint from [hostfingerprints] and add the following entry to the new [hostsecurity] section: localhost:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e)
312 312 5fed3813f7f5
313 313
314 314 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, sha1:ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03' -R copy-pull id https://localhost:$HGPORT/
315 315 5fed3813f7f5
316 316
317 317 - multiple fingerprints specified and none match
318 318
319 319 $ hg --config 'hostfingerprints.localhost=deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, aeadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/ --insecure
320 320 abort: certificate for localhost has unexpected fingerprint ec:d8:7c:d6:b3:86:d0:4f:c1:b8:b4:1c:9d:8f:5e:16:8e:ef:1c:03
321 321 (check hostfingerprint configuration)
322 322 [150]
323 323
324 324 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, sha1:aeadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/
325 325 abort: certificate for localhost has unexpected fingerprint sha1:ec:d8:7c:d6:b3:86:d0:4f:c1:b8:b4:1c:9d:8f:5e:16:8e:ef:1c:03
326 326 (check hostsecurity configuration)
327 327 [150]
328 328
329 329 - fails when cert doesn't match hostname (port is ignored)
330 330 $ hg -R copy-pull id https://localhost:$HGPORT1/ --config hostfingerprints.localhost=ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03
331 331 abort: certificate for localhost has unexpected fingerprint f4:2f:5a:0c:3e:52:5b:db:e7:24:a8:32:1d:18:97:6d:69:b5:87:84
332 332 (check hostfingerprint configuration)
333 333 [150]
334 334
335 335
336 336 - ignores that certificate doesn't match hostname
337 337 $ hg -R copy-pull id https://$LOCALIP:$HGPORT/ --config hostfingerprints.$LOCALIP=ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03
338 338 (SHA-1 fingerprint for $LOCALIP found in legacy [hostfingerprints] section; if you trust this fingerprint, remove the old SHA-1 fingerprint from [hostfingerprints] and add the following entry to the new [hostsecurity] section: $LOCALIP:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e)
339 339 5fed3813f7f5
340 340
341 341 Ports used by next test. Kill servers.
342 342
343 343 $ killdaemons.py hg0.pid
344 344 $ killdaemons.py hg1.pid
345 345 $ killdaemons.py hg2.pid
346 346
347 347 #if tls1.2
348 348 Start servers running supported TLS versions
349 349
350 350 $ cd test
351 351 $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV \
352 352 > --config devel.serverexactprotocol=tls1.0
353 353 $ cat ../hg0.pid >> $DAEMON_PIDS
354 354 $ hg serve -p $HGPORT1 -d --pid-file=../hg1.pid --certificate=$PRIV \
355 355 > --config devel.serverexactprotocol=tls1.1
356 356 $ cat ../hg1.pid >> $DAEMON_PIDS
357 357 $ hg serve -p $HGPORT2 -d --pid-file=../hg2.pid --certificate=$PRIV \
358 358 > --config devel.serverexactprotocol=tls1.2
359 359 $ cat ../hg2.pid >> $DAEMON_PIDS
360 360 $ cd ..
361 361
362 362 Clients talking same TLS versions work
363 363
364 364 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.0 id https://localhost:$HGPORT/
365 365 5fed3813f7f5
366 366 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.1 id https://localhost:$HGPORT1/
367 367 5fed3813f7f5
368 368 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.2 id https://localhost:$HGPORT2/
369 369 5fed3813f7f5
370 370
371 371 Clients requiring newer TLS version than what server supports fail
372 372
373 373 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/
374 374 (could not negotiate a common security protocol (tls1.1+) with localhost; the likely cause is Mercurial is configured to be more secure than the server can support)
375 375 (consider contacting the operator of this server and ask them to support modern TLS protocol versions; or, set hostsecurity.localhost:minimumprotocol=tls1.0 to allow use of legacy, less secure protocols when communicating with this server)
376 376 (see https://mercurial-scm.org/wiki/SecureConnections for more info)
377 377 abort: error: .*(unsupported protocol|wrong ssl version).* (re)
378 378 [100]
379 379
380 380 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.1 id https://localhost:$HGPORT/
381 381 (could not negotiate a common security protocol (tls1.1+) with localhost; the likely cause is Mercurial is configured to be more secure than the server can support)
382 382 (consider contacting the operator of this server and ask them to support modern TLS protocol versions; or, set hostsecurity.localhost:minimumprotocol=tls1.0 to allow use of legacy, less secure protocols when communicating with this server)
383 383 (see https://mercurial-scm.org/wiki/SecureConnections for more info)
384 384 abort: error: .*(unsupported protocol|wrong ssl version).* (re)
385 385 [100]
386 386 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.2 id https://localhost:$HGPORT/
387 387 (could not negotiate a common security protocol (tls1.2+) with localhost; the likely cause is Mercurial is configured to be more secure than the server can support)
388 388 (consider contacting the operator of this server and ask them to support modern TLS protocol versions; or, set hostsecurity.localhost:minimumprotocol=tls1.0 to allow use of legacy, less secure protocols when communicating with this server)
389 389 (see https://mercurial-scm.org/wiki/SecureConnections for more info)
390 390 abort: error: .*(unsupported protocol|wrong ssl version).* (re)
391 391 [100]
392 392 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.2 id https://localhost:$HGPORT1/
393 393 (could not negotiate a common security protocol (tls1.2+) with localhost; the likely cause is Mercurial is configured to be more secure than the server can support)
394 394 (consider contacting the operator of this server and ask them to support modern TLS protocol versions; or, set hostsecurity.localhost:minimumprotocol=tls1.0 to allow use of legacy, less secure protocols when communicating with this server)
395 395 (see https://mercurial-scm.org/wiki/SecureConnections for more info)
396 396 abort: error: .*(unsupported protocol|wrong ssl version).* (re)
397 397 [100]
398 398
399 399 --insecure will allow TLS 1.0 connections and override configs
400 400
401 401 $ hg --config hostsecurity.minimumprotocol=tls1.2 id --insecure https://localhost:$HGPORT1/
402 402 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
403 403 5fed3813f7f5
404 404
405 405 The per-host config option overrides the default
406 406
407 407 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
408 408 > --config hostsecurity.minimumprotocol=tls1.2 \
409 409 > --config hostsecurity.localhost:minimumprotocol=tls1.0
410 410 5fed3813f7f5
411 411
412 412 The per-host config option by itself works
413 413
414 414 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
415 415 > --config hostsecurity.localhost:minimumprotocol=tls1.2
416 416 (could not negotiate a common security protocol (tls1.2+) with localhost; the likely cause is Mercurial is configured to be more secure than the server can support)
417 417 (consider contacting the operator of this server and ask them to support modern TLS protocol versions; or, set hostsecurity.localhost:minimumprotocol=tls1.0 to allow use of legacy, less secure protocols when communicating with this server)
418 418 (see https://mercurial-scm.org/wiki/SecureConnections for more info)
419 419 abort: error: .*(unsupported protocol|wrong ssl version).* (re)
420 420 [100]
421 421
422 422 .hg/hgrc file [hostsecurity] settings are applied to remote ui instances (issue5305)
423 423
424 424 $ cat >> copy-pull/.hg/hgrc << EOF
425 425 > [hostsecurity]
426 426 > localhost:minimumprotocol=tls1.2
427 427 > EOF
428 428 $ P="$CERTSDIR" hg -R copy-pull id https://localhost:$HGPORT/
429 429 (could not negotiate a common security protocol (tls1.2+) with localhost; the likely cause is Mercurial is configured to be more secure than the server can support)
430 430 (consider contacting the operator of this server and ask them to support modern TLS protocol versions; or, set hostsecurity.localhost:minimumprotocol=tls1.0 to allow use of legacy, less secure protocols when communicating with this server)
431 431 (see https://mercurial-scm.org/wiki/SecureConnections for more info)
432 432 abort: error: .*(unsupported protocol|wrong ssl version).* (re)
433 433 [100]
434 434
435 435 $ killdaemons.py hg0.pid
436 436 $ killdaemons.py hg1.pid
437 437 $ killdaemons.py hg2.pid
438 438 #endif
439 439
440 440 Prepare for connecting through proxy
441 441
442 442 $ hg serve -R test -p $HGPORT -d --pid-file=hg0.pid --certificate=$PRIV
443 443 $ cat hg0.pid >> $DAEMON_PIDS
444 444 $ hg serve -R test -p $HGPORT2 -d --pid-file=hg2.pid --certificate=server-expired.pem
445 445 $ cat hg2.pid >> $DAEMON_PIDS
446 446 tinyproxy.py doesn't fully detach, so killing it may result in extra output
447 447 from the shell. So don't kill it.
448 448 $ tinyproxy.py $HGPORT1 localhost >proxy.log </dev/null 2>&1 &
449 449 $ while [ ! -f proxy.pid ]; do sleep 0; done
450 450 $ cat proxy.pid >> $DAEMON_PIDS
451 451
452 452 $ echo "[http_proxy]" >> copy-pull/.hg/hgrc
453 453 $ echo "always=True" >> copy-pull/.hg/hgrc
454 454 $ echo "[hostfingerprints]" >> copy-pull/.hg/hgrc
455 455 $ echo "localhost =" >> copy-pull/.hg/hgrc
456 456
457 457 Test unvalidated https through proxy
458 458
459 459 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --insecure
460 460 pulling from https://localhost:$HGPORT/
461 461 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
462 462 searching for changes
463 463 no changes found
464 464
465 465 Test https with cacert and fingerprint through proxy
466 466
467 467 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull \
468 468 > --config web.cacerts="$CERTSDIR/pub.pem"
469 469 pulling from https://localhost:$HGPORT/
470 470 searching for changes
471 471 no changes found
472 472 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull https://localhost:$HGPORT/ --config hostfingerprints.localhost=ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03 --trace
473 473 pulling from https://*:$HGPORT/ (glob)
474 474 (SHA-1 fingerprint for localhost found in legacy [hostfingerprints] section; if you trust this fingerprint, remove the old SHA-1 fingerprint from [hostfingerprints] and add the following entry to the new [hostsecurity] section: localhost:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e)
475 475 searching for changes
476 476 no changes found
477 477
478 478 Test https with cert problems through proxy
479 479
480 480 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull \
481 481 > --config web.cacerts="$CERTSDIR/pub-other.pem"
482 482 pulling from https://localhost:$HGPORT/
483 483 (the full certificate chain may not be available locally; see "hg help debugssl") (windows !)
484 484 abort: error: *certificate verify failed* (glob)
485 485 [100]
486 486 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull \
487 487 > --config web.cacerts="$CERTSDIR/pub-expired.pem" https://localhost:$HGPORT2/
488 488 pulling from https://localhost:$HGPORT2/
489 489 (the full certificate chain may not be available locally; see "hg help debugssl") (windows !)
490 490 abort: error: *certificate verify failed* (glob)
491 491 [100]
492 492
493 Test when proxy can't connect to server
494
495 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --insecure https://localhost:0/
496 pulling from https://localhost:0/
497 abort: error: Tunnel connection failed: 404 Connection refused
498 [100]
499
493 500
494 501 $ killdaemons.py hg0.pid
495 502
496 503 $ cd test
497 504
498 505 Missing certificate file(s) are detected
499 506
500 507 $ hg serve -p $HGPORT --certificate=/missing/certificate \
501 508 > --config devel.servercafile=$PRIV --config devel.serverrequirecert=true
502 509 abort: referenced certificate file (*/missing/certificate) does not exist (glob)
503 510 [255]
504 511
505 512 $ hg serve -p $HGPORT --certificate=$PRIV \
506 513 > --config devel.servercafile=/missing/cafile --config devel.serverrequirecert=true
507 514 abort: referenced certificate file (*/missing/cafile) does not exist (glob)
508 515 [255]
509 516
510 517 Start hgweb that requires client certificates:
511 518
512 519 $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV \
513 520 > --config devel.servercafile=$PRIV --config devel.serverrequirecert=true
514 521 $ cat ../hg0.pid >> $DAEMON_PIDS
515 522 $ cd ..
516 523
517 524 without client certificate:
518 525
519 526 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/
520 527 abort: error: .*(\$ECONNRESET\$|certificate required|handshake failure).* (re)
521 528 [100]
522 529
523 530 with client certificate:
524 531
525 532 $ cat << EOT >> $HGRCPATH
526 533 > [auth]
527 534 > l.prefix = localhost
528 535 > l.cert = $CERTSDIR/client-cert.pem
529 536 > l.key = $CERTSDIR/client-key.pem
530 537 > EOT
531 538
532 539 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
533 540 > --config auth.l.key="$CERTSDIR/client-key-decrypted.pem"
534 541 5fed3813f7f5
535 542
536 543 $ printf '1234\n' | env P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
537 544 > --config ui.interactive=True --config ui.nontty=True
538 545 passphrase for */client-key.pem: 5fed3813f7f5 (glob)
539 546
540 547 $ env P="$CERTSDIR" hg id https://localhost:$HGPORT/
541 548 abort: error: * (glob)
542 549 [100]
543 550
544 551 Missing certficate and key files result in error
545 552
546 553 $ hg id https://localhost:$HGPORT/ --config auth.l.cert=/missing/cert
547 554 abort: certificate file (*/missing/cert) does not exist; cannot connect to localhost (glob)
548 555 (restore missing file or fix references in Mercurial config)
549 556 [255]
550 557
551 558 $ hg id https://localhost:$HGPORT/ --config auth.l.key=/missing/key
552 559 abort: certificate file (*/missing/key) does not exist; cannot connect to localhost (glob)
553 560 (restore missing file or fix references in Mercurial config)
554 561 [255]
General Comments 0
You need to be logged in to leave comments. Login now