##// END OF EJS Templates
cacert: improve error report when web.cacert file does not exist
timeless -
r13544:66d65bcc stable
parent child Browse files
Show More
@@ -1,758 +1,762 b''
1 1 # url.py - HTTP handling for mercurial
2 2 #
3 3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@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 import urllib, urllib2, urlparse, httplib, os, re, socket, cStringIO
11 11 import __builtin__
12 12 from i18n import _
13 13 import keepalive, util
14 14
15 15 def _urlunparse(scheme, netloc, path, params, query, fragment, url):
16 16 '''Handle cases where urlunparse(urlparse(x://)) doesn't preserve the "//"'''
17 17 result = urlparse.urlunparse((scheme, netloc, path, params, query, fragment))
18 18 if (scheme and
19 19 result.startswith(scheme + ':') and
20 20 not result.startswith(scheme + '://') and
21 21 url.startswith(scheme + '://')
22 22 ):
23 23 result = scheme + '://' + result[len(scheme + ':'):]
24 24 return result
25 25
26 26 def hidepassword(url):
27 27 '''hide user credential in a url string'''
28 28 scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
29 29 netloc = re.sub('([^:]*):([^@]*)@(.*)', r'\1:***@\3', netloc)
30 30 return _urlunparse(scheme, netloc, path, params, query, fragment, url)
31 31
32 32 def removeauth(url):
33 33 '''remove all authentication information from a url string'''
34 34 scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
35 35 netloc = netloc[netloc.find('@')+1:]
36 36 return _urlunparse(scheme, netloc, path, params, query, fragment, url)
37 37
38 38 def netlocsplit(netloc):
39 39 '''split [user[:passwd]@]host[:port] into 4-tuple.'''
40 40
41 41 a = netloc.find('@')
42 42 if a == -1:
43 43 user, passwd = None, None
44 44 else:
45 45 userpass, netloc = netloc[:a], netloc[a + 1:]
46 46 c = userpass.find(':')
47 47 if c == -1:
48 48 user, passwd = urllib.unquote(userpass), None
49 49 else:
50 50 user = urllib.unquote(userpass[:c])
51 51 passwd = urllib.unquote(userpass[c + 1:])
52 52 c = netloc.find(':')
53 53 if c == -1:
54 54 host, port = netloc, None
55 55 else:
56 56 host, port = netloc[:c], netloc[c + 1:]
57 57 return host, port, user, passwd
58 58
59 59 def netlocunsplit(host, port, user=None, passwd=None):
60 60 '''turn host, port, user, passwd into [user[:passwd]@]host[:port].'''
61 61 if port:
62 62 hostport = host + ':' + port
63 63 else:
64 64 hostport = host
65 65 if user:
66 66 quote = lambda s: urllib.quote(s, safe='')
67 67 if passwd:
68 68 userpass = quote(user) + ':' + quote(passwd)
69 69 else:
70 70 userpass = quote(user)
71 71 return userpass + '@' + hostport
72 72 return hostport
73 73
74 74 def readauthforuri(ui, uri):
75 75 # Read configuration
76 76 config = dict()
77 77 for key, val in ui.configitems('auth'):
78 78 if '.' not in key:
79 79 ui.warn(_("ignoring invalid [auth] key '%s'\n") % key)
80 80 continue
81 81 group, setting = key.rsplit('.', 1)
82 82 gdict = config.setdefault(group, dict())
83 83 if setting in ('username', 'cert', 'key'):
84 84 val = util.expandpath(val)
85 85 gdict[setting] = val
86 86
87 87 # Find the best match
88 88 scheme, hostpath = uri.split('://', 1)
89 89 bestlen = 0
90 90 bestauth = None
91 91 for group, auth in config.iteritems():
92 92 prefix = auth.get('prefix')
93 93 if not prefix:
94 94 continue
95 95 p = prefix.split('://', 1)
96 96 if len(p) > 1:
97 97 schemes, prefix = [p[0]], p[1]
98 98 else:
99 99 schemes = (auth.get('schemes') or 'https').split()
100 100 if (prefix == '*' or hostpath.startswith(prefix)) and \
101 101 len(prefix) > bestlen and scheme in schemes:
102 102 bestlen = len(prefix)
103 103 bestauth = group, auth
104 104 return bestauth
105 105
106 106 _safe = ('abcdefghijklmnopqrstuvwxyz'
107 107 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
108 108 '0123456789' '_.-/')
109 109 _safeset = None
110 110 _hex = None
111 111 def quotepath(path):
112 112 '''quote the path part of a URL
113 113
114 114 This is similar to urllib.quote, but it also tries to avoid
115 115 quoting things twice (inspired by wget):
116 116
117 117 >>> quotepath('abc def')
118 118 'abc%20def'
119 119 >>> quotepath('abc%20def')
120 120 'abc%20def'
121 121 >>> quotepath('abc%20 def')
122 122 'abc%20%20def'
123 123 >>> quotepath('abc def%20')
124 124 'abc%20def%20'
125 125 >>> quotepath('abc def%2')
126 126 'abc%20def%252'
127 127 >>> quotepath('abc def%')
128 128 'abc%20def%25'
129 129 '''
130 130 global _safeset, _hex
131 131 if _safeset is None:
132 132 _safeset = set(_safe)
133 133 _hex = set('abcdefABCDEF0123456789')
134 134 l = list(path)
135 135 for i in xrange(len(l)):
136 136 c = l[i]
137 137 if (c == '%' and i + 2 < len(l) and
138 138 l[i + 1] in _hex and l[i + 2] in _hex):
139 139 pass
140 140 elif c not in _safeset:
141 141 l[i] = '%%%02X' % ord(c)
142 142 return ''.join(l)
143 143
144 144 class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
145 145 def __init__(self, ui):
146 146 urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
147 147 self.ui = ui
148 148
149 149 def find_user_password(self, realm, authuri):
150 150 authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
151 151 self, realm, authuri)
152 152 user, passwd = authinfo
153 153 if user and passwd:
154 154 self._writedebug(user, passwd)
155 155 return (user, passwd)
156 156
157 157 if not user:
158 158 res = readauthforuri(self.ui, authuri)
159 159 if res:
160 160 group, auth = res
161 161 user, passwd = auth.get('username'), auth.get('password')
162 162 self.ui.debug("using auth.%s.* for authentication\n" % group)
163 163 if not user or not passwd:
164 164 if not self.ui.interactive():
165 165 raise util.Abort(_('http authorization required'))
166 166
167 167 self.ui.write(_("http authorization required\n"))
168 168 self.ui.write(_("realm: %s\n") % realm)
169 169 if user:
170 170 self.ui.write(_("user: %s\n") % user)
171 171 else:
172 172 user = self.ui.prompt(_("user:"), default=None)
173 173
174 174 if not passwd:
175 175 passwd = self.ui.getpass()
176 176
177 177 self.add_password(realm, authuri, user, passwd)
178 178 self._writedebug(user, passwd)
179 179 return (user, passwd)
180 180
181 181 def _writedebug(self, user, passwd):
182 182 msg = _('http auth: user %s, password %s\n')
183 183 self.ui.debug(msg % (user, passwd and '*' * len(passwd) or 'not set'))
184 184
185 185 class proxyhandler(urllib2.ProxyHandler):
186 186 def __init__(self, ui):
187 187 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
188 188 # XXX proxyauthinfo = None
189 189
190 190 if proxyurl:
191 191 # proxy can be proper url or host[:port]
192 192 if not (proxyurl.startswith('http:') or
193 193 proxyurl.startswith('https:')):
194 194 proxyurl = 'http://' + proxyurl + '/'
195 195 snpqf = urlparse.urlsplit(proxyurl)
196 196 proxyscheme, proxynetloc, proxypath, proxyquery, proxyfrag = snpqf
197 197 hpup = netlocsplit(proxynetloc)
198 198
199 199 proxyhost, proxyport, proxyuser, proxypasswd = hpup
200 200 if not proxyuser:
201 201 proxyuser = ui.config("http_proxy", "user")
202 202 proxypasswd = ui.config("http_proxy", "passwd")
203 203
204 204 # see if we should use a proxy for this url
205 205 no_list = ["localhost", "127.0.0.1"]
206 206 no_list.extend([p.lower() for
207 207 p in ui.configlist("http_proxy", "no")])
208 208 no_list.extend([p.strip().lower() for
209 209 p in os.getenv("no_proxy", '').split(',')
210 210 if p.strip()])
211 211 # "http_proxy.always" config is for running tests on localhost
212 212 if ui.configbool("http_proxy", "always"):
213 213 self.no_list = []
214 214 else:
215 215 self.no_list = no_list
216 216
217 217 proxyurl = urlparse.urlunsplit((
218 218 proxyscheme, netlocunsplit(proxyhost, proxyport,
219 219 proxyuser, proxypasswd or ''),
220 220 proxypath, proxyquery, proxyfrag))
221 221 proxies = {'http': proxyurl, 'https': proxyurl}
222 222 ui.debug('proxying through http://%s:%s\n' %
223 223 (proxyhost, proxyport))
224 224 else:
225 225 proxies = {}
226 226
227 227 # urllib2 takes proxy values from the environment and those
228 228 # will take precedence if found, so drop them
229 229 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
230 230 try:
231 231 if env in os.environ:
232 232 del os.environ[env]
233 233 except OSError:
234 234 pass
235 235
236 236 urllib2.ProxyHandler.__init__(self, proxies)
237 237 self.ui = ui
238 238
239 239 def proxy_open(self, req, proxy, type_):
240 240 host = req.get_host().split(':')[0]
241 241 if host in self.no_list:
242 242 return None
243 243
244 244 # work around a bug in Python < 2.4.2
245 245 # (it leaves a "\n" at the end of Proxy-authorization headers)
246 246 baseclass = req.__class__
247 247 class _request(baseclass):
248 248 def add_header(self, key, val):
249 249 if key.lower() == 'proxy-authorization':
250 250 val = val.strip()
251 251 return baseclass.add_header(self, key, val)
252 252 req.__class__ = _request
253 253
254 254 return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_)
255 255
256 256 class httpsendfile(object):
257 257 """This is a wrapper around the objects returned by python's "open".
258 258
259 259 Its purpose is to send file-like objects via HTTP and, to do so, it
260 260 defines a __len__ attribute to feed the Content-Length header.
261 261 """
262 262
263 263 def __init__(self, ui, *args, **kwargs):
264 264 # We can't just "self._data = open(*args, **kwargs)" here because there
265 265 # is an "open" function defined in this module that shadows the global
266 266 # one
267 267 self.ui = ui
268 268 self._data = __builtin__.open(*args, **kwargs)
269 269 self.seek = self._data.seek
270 270 self.close = self._data.close
271 271 self.write = self._data.write
272 272 self._len = os.fstat(self._data.fileno()).st_size
273 273 self._pos = 0
274 274 self._total = len(self) / 1024 * 2
275 275
276 276 def read(self, *args, **kwargs):
277 277 try:
278 278 ret = self._data.read(*args, **kwargs)
279 279 except EOFError:
280 280 self.ui.progress(_('sending'), None)
281 281 self._pos += len(ret)
282 282 # We pass double the max for total because we currently have
283 283 # to send the bundle twice in the case of a server that
284 284 # requires authentication. Since we can't know until we try
285 285 # once whether authentication will be required, just lie to
286 286 # the user and maybe the push succeeds suddenly at 50%.
287 287 self.ui.progress(_('sending'), self._pos / 1024,
288 288 unit=_('kb'), total=self._total)
289 289 return ret
290 290
291 291 def __len__(self):
292 292 return self._len
293 293
294 294 def _gen_sendfile(orgsend):
295 295 def _sendfile(self, data):
296 296 # send a file
297 297 if isinstance(data, httpsendfile):
298 298 # if auth required, some data sent twice, so rewind here
299 299 data.seek(0)
300 300 for chunk in util.filechunkiter(data):
301 301 orgsend(self, chunk)
302 302 else:
303 303 orgsend(self, data)
304 304 return _sendfile
305 305
306 306 has_https = hasattr(urllib2, 'HTTPSHandler')
307 307 if has_https:
308 308 try:
309 309 # avoid using deprecated/broken FakeSocket in python 2.6
310 310 import ssl
311 311 _ssl_wrap_socket = ssl.wrap_socket
312 312 CERT_REQUIRED = ssl.CERT_REQUIRED
313 313 except ImportError:
314 314 CERT_REQUIRED = 2
315 315
316 316 def _ssl_wrap_socket(sock, key_file, cert_file,
317 317 cert_reqs=CERT_REQUIRED, ca_certs=None):
318 318 if ca_certs:
319 319 raise util.Abort(_(
320 320 'certificate checking requires Python 2.6'))
321 321
322 322 ssl = socket.ssl(sock, key_file, cert_file)
323 323 return httplib.FakeSocket(sock, ssl)
324 324
325 325 try:
326 326 _create_connection = socket.create_connection
327 327 except AttributeError:
328 328 _GLOBAL_DEFAULT_TIMEOUT = object()
329 329
330 330 def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
331 331 source_address=None):
332 332 # lifted from Python 2.6
333 333
334 334 msg = "getaddrinfo returns an empty list"
335 335 host, port = address
336 336 for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
337 337 af, socktype, proto, canonname, sa = res
338 338 sock = None
339 339 try:
340 340 sock = socket.socket(af, socktype, proto)
341 341 if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
342 342 sock.settimeout(timeout)
343 343 if source_address:
344 344 sock.bind(source_address)
345 345 sock.connect(sa)
346 346 return sock
347 347
348 348 except socket.error, msg:
349 349 if sock is not None:
350 350 sock.close()
351 351
352 352 raise socket.error, msg
353 353
354 354 class httpconnection(keepalive.HTTPConnection):
355 355 # must be able to send big bundle as stream.
356 356 send = _gen_sendfile(keepalive.HTTPConnection.send)
357 357
358 358 def connect(self):
359 359 if has_https and self.realhostport: # use CONNECT proxy
360 360 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
361 361 self.sock.connect((self.host, self.port))
362 362 if _generic_proxytunnel(self):
363 363 # we do not support client x509 certificates
364 364 self.sock = _ssl_wrap_socket(self.sock, None, None)
365 365 else:
366 366 keepalive.HTTPConnection.connect(self)
367 367
368 368 def getresponse(self):
369 369 proxyres = getattr(self, 'proxyres', None)
370 370 if proxyres:
371 371 if proxyres.will_close:
372 372 self.close()
373 373 self.proxyres = None
374 374 return proxyres
375 375 return keepalive.HTTPConnection.getresponse(self)
376 376
377 377 # general transaction handler to support different ways to handle
378 378 # HTTPS proxying before and after Python 2.6.3.
379 379 def _generic_start_transaction(handler, h, req):
380 380 if hasattr(req, '_tunnel_host') and req._tunnel_host:
381 381 tunnel_host = req._tunnel_host
382 382 if tunnel_host[:7] not in ['http://', 'https:/']:
383 383 tunnel_host = 'https://' + tunnel_host
384 384 new_tunnel = True
385 385 else:
386 386 tunnel_host = req.get_selector()
387 387 new_tunnel = False
388 388
389 389 if new_tunnel or tunnel_host == req.get_full_url(): # has proxy
390 390 urlparts = urlparse.urlparse(tunnel_host)
391 391 if new_tunnel or urlparts[0] == 'https': # only use CONNECT for HTTPS
392 392 realhostport = urlparts[1]
393 393 if realhostport[-1] == ']' or ':' not in realhostport:
394 394 realhostport += ':443'
395 395
396 396 h.realhostport = realhostport
397 397 h.headers = req.headers.copy()
398 398 h.headers.update(handler.parent.addheaders)
399 399 return
400 400
401 401 h.realhostport = None
402 402 h.headers = None
403 403
404 404 def _generic_proxytunnel(self):
405 405 proxyheaders = dict(
406 406 [(x, self.headers[x]) for x in self.headers
407 407 if x.lower().startswith('proxy-')])
408 408 self._set_hostport(self.host, self.port)
409 409 self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport)
410 410 for header in proxyheaders.iteritems():
411 411 self.send('%s: %s\r\n' % header)
412 412 self.send('\r\n')
413 413
414 414 # majority of the following code is duplicated from
415 415 # httplib.HTTPConnection as there are no adequate places to
416 416 # override functions to provide the needed functionality
417 417 res = self.response_class(self.sock,
418 418 strict=self.strict,
419 419 method=self._method)
420 420
421 421 while True:
422 422 version, status, reason = res._read_status()
423 423 if status != httplib.CONTINUE:
424 424 break
425 425 while True:
426 426 skip = res.fp.readline().strip()
427 427 if not skip:
428 428 break
429 429 res.status = status
430 430 res.reason = reason.strip()
431 431
432 432 if res.status == 200:
433 433 while True:
434 434 line = res.fp.readline()
435 435 if line == '\r\n':
436 436 break
437 437 return True
438 438
439 439 if version == 'HTTP/1.0':
440 440 res.version = 10
441 441 elif version.startswith('HTTP/1.'):
442 442 res.version = 11
443 443 elif version == 'HTTP/0.9':
444 444 res.version = 9
445 445 else:
446 446 raise httplib.UnknownProtocol(version)
447 447
448 448 if res.version == 9:
449 449 res.length = None
450 450 res.chunked = 0
451 451 res.will_close = 1
452 452 res.msg = httplib.HTTPMessage(cStringIO.StringIO())
453 453 return False
454 454
455 455 res.msg = httplib.HTTPMessage(res.fp)
456 456 res.msg.fp = None
457 457
458 458 # are we using the chunked-style of transfer encoding?
459 459 trenc = res.msg.getheader('transfer-encoding')
460 460 if trenc and trenc.lower() == "chunked":
461 461 res.chunked = 1
462 462 res.chunk_left = None
463 463 else:
464 464 res.chunked = 0
465 465
466 466 # will the connection close at the end of the response?
467 467 res.will_close = res._check_close()
468 468
469 469 # do we have a Content-Length?
470 470 # NOTE: RFC 2616, S4.4, #3 says we ignore this if tr_enc is "chunked"
471 471 length = res.msg.getheader('content-length')
472 472 if length and not res.chunked:
473 473 try:
474 474 res.length = int(length)
475 475 except ValueError:
476 476 res.length = None
477 477 else:
478 478 if res.length < 0: # ignore nonsensical negative lengths
479 479 res.length = None
480 480 else:
481 481 res.length = None
482 482
483 483 # does the body have a fixed length? (of zero)
484 484 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
485 485 100 <= status < 200 or # 1xx codes
486 486 res._method == 'HEAD'):
487 487 res.length = 0
488 488
489 489 # if the connection remains open, and we aren't using chunked, and
490 490 # a content-length was not provided, then assume that the connection
491 491 # WILL close.
492 492 if (not res.will_close and
493 493 not res.chunked and
494 494 res.length is None):
495 495 res.will_close = 1
496 496
497 497 self.proxyres = res
498 498
499 499 return False
500 500
501 501 class httphandler(keepalive.HTTPHandler):
502 502 def http_open(self, req):
503 503 return self.do_open(httpconnection, req)
504 504
505 505 def _start_transaction(self, h, req):
506 506 _generic_start_transaction(self, h, req)
507 507 return keepalive.HTTPHandler._start_transaction(self, h, req)
508 508
509 509 def _verifycert(cert, hostname):
510 510 '''Verify that cert (in socket.getpeercert() format) matches hostname.
511 511 CRLs is not handled.
512 512
513 513 Returns error message if any problems are found and None on success.
514 514 '''
515 515 if not cert:
516 516 return _('no certificate received')
517 517 dnsname = hostname.lower()
518 518 def matchdnsname(certname):
519 519 return (certname == dnsname or
520 520 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1])
521 521
522 522 san = cert.get('subjectAltName', [])
523 523 if san:
524 524 certnames = [value.lower() for key, value in san if key == 'DNS']
525 525 for name in certnames:
526 526 if matchdnsname(name):
527 527 return None
528 528 return _('certificate is for %s') % ', '.join(certnames)
529 529
530 530 # subject is only checked when subjectAltName is empty
531 531 for s in cert.get('subject', []):
532 532 key, value = s[0]
533 533 if key == 'commonName':
534 534 try:
535 535 # 'subject' entries are unicode
536 536 certname = value.lower().encode('ascii')
537 537 except UnicodeEncodeError:
538 538 return _('IDN in certificate not supported')
539 539 if matchdnsname(certname):
540 540 return None
541 541 return _('certificate is for %s') % certname
542 542 return _('no commonName or subjectAltName found in certificate')
543 543
544 544 if has_https:
545 545 class httpsconnection(httplib.HTTPSConnection):
546 546 response_class = keepalive.HTTPResponse
547 547 # must be able to send big bundle as stream.
548 548 send = _gen_sendfile(keepalive.safesend)
549 549 getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection)
550 550
551 551 def connect(self):
552 552 self.sock = _create_connection((self.host, self.port))
553 553
554 554 host = self.host
555 555 if self.realhostport: # use CONNECT proxy
556 556 something = _generic_proxytunnel(self)
557 557 host = self.realhostport.rsplit(':', 1)[0]
558 558
559 559 cacerts = self.ui.config('web', 'cacerts')
560 560 hostfingerprint = self.ui.config('hostfingerprints', host)
561 561
562 562 if cacerts and not hostfingerprint:
563 cacerts = util.expandpath(cacerts)
564 if not os.path.exists(cacerts):
565 raise util.Abort(_('could not find '
566 'web.cacerts: %s') % cacerts)
563 567 self.sock = _ssl_wrap_socket(self.sock, self.key_file,
564 568 self.cert_file, cert_reqs=CERT_REQUIRED,
565 ca_certs=util.expandpath(cacerts))
569 ca_certs=cacerts)
566 570 msg = _verifycert(self.sock.getpeercert(), host)
567 571 if msg:
568 572 raise util.Abort(_('%s certificate error: %s '
569 573 '(use --insecure to connect '
570 574 'insecurely)') % (host, msg))
571 575 self.ui.debug('%s certificate successfully verified\n' % host)
572 576 else:
573 577 self.sock = _ssl_wrap_socket(self.sock, self.key_file,
574 578 self.cert_file)
575 579 if hasattr(self.sock, 'getpeercert'):
576 580 peercert = self.sock.getpeercert(True)
577 581 peerfingerprint = util.sha1(peercert).hexdigest()
578 582 nicefingerprint = ":".join([peerfingerprint[x:x + 2]
579 583 for x in xrange(0, len(peerfingerprint), 2)])
580 584 if hostfingerprint:
581 585 if peerfingerprint.lower() != \
582 586 hostfingerprint.replace(':', '').lower():
583 587 raise util.Abort(_('invalid certificate for %s '
584 588 'with fingerprint %s') %
585 589 (host, nicefingerprint))
586 590 self.ui.debug('%s certificate matched fingerprint %s\n' %
587 591 (host, nicefingerprint))
588 592 else:
589 593 self.ui.warn(_('warning: %s certificate '
590 594 'with fingerprint %s not verified '
591 595 '(check hostfingerprints or web.cacerts '
592 596 'config setting)\n') %
593 597 (host, nicefingerprint))
594 598 else: # python 2.5 ?
595 599 if hostfingerprint:
596 600 raise util.Abort(_('no certificate for %s with '
597 601 'configured hostfingerprint') % host)
598 602 self.ui.warn(_('warning: %s certificate not verified '
599 603 '(check web.cacerts config setting)\n') %
600 604 host)
601 605
602 606 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
603 607 def __init__(self, ui):
604 608 keepalive.KeepAliveHandler.__init__(self)
605 609 urllib2.HTTPSHandler.__init__(self)
606 610 self.ui = ui
607 611 self.pwmgr = passwordmgr(self.ui)
608 612
609 613 def _start_transaction(self, h, req):
610 614 _generic_start_transaction(self, h, req)
611 615 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
612 616
613 617 def https_open(self, req):
614 618 res = readauthforuri(self.ui, req.get_full_url())
615 619 if res:
616 620 group, auth = res
617 621 self.auth = auth
618 622 self.ui.debug("using auth.%s.* for authentication\n" % group)
619 623 else:
620 624 self.auth = None
621 625 return self.do_open(self._makeconnection, req)
622 626
623 627 def _makeconnection(self, host, port=None, *args, **kwargs):
624 628 keyfile = None
625 629 certfile = None
626 630
627 631 if len(args) >= 1: # key_file
628 632 keyfile = args[0]
629 633 if len(args) >= 2: # cert_file
630 634 certfile = args[1]
631 635 args = args[2:]
632 636
633 637 # if the user has specified different key/cert files in
634 638 # hgrc, we prefer these
635 639 if self.auth and 'key' in self.auth and 'cert' in self.auth:
636 640 keyfile = self.auth['key']
637 641 certfile = self.auth['cert']
638 642
639 643 conn = httpsconnection(host, port, keyfile, certfile, *args, **kwargs)
640 644 conn.ui = self.ui
641 645 return conn
642 646
643 647 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
644 648 def __init__(self, *args, **kwargs):
645 649 urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
646 650 self.retried_req = None
647 651
648 652 def reset_retry_count(self):
649 653 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
650 654 # forever. We disable reset_retry_count completely and reset in
651 655 # http_error_auth_reqed instead.
652 656 pass
653 657
654 658 def http_error_auth_reqed(self, auth_header, host, req, headers):
655 659 # Reset the retry counter once for each request.
656 660 if req is not self.retried_req:
657 661 self.retried_req = req
658 662 self.retried = 0
659 663 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
660 664 # it doesn't know about the auth type requested. This can happen if
661 665 # somebody is using BasicAuth and types a bad password.
662 666 try:
663 667 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
664 668 self, auth_header, host, req, headers)
665 669 except ValueError, inst:
666 670 arg = inst.args[0]
667 671 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
668 672 return
669 673 raise
670 674
671 675 class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler):
672 676 def __init__(self, *args, **kwargs):
673 677 urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
674 678 self.retried_req = None
675 679
676 680 def reset_retry_count(self):
677 681 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
678 682 # forever. We disable reset_retry_count completely and reset in
679 683 # http_error_auth_reqed instead.
680 684 pass
681 685
682 686 def http_error_auth_reqed(self, auth_header, host, req, headers):
683 687 # Reset the retry counter once for each request.
684 688 if req is not self.retried_req:
685 689 self.retried_req = req
686 690 self.retried = 0
687 691 return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
688 692 self, auth_header, host, req, headers)
689 693
690 694 def getauthinfo(path):
691 695 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
692 696 if not urlpath:
693 697 urlpath = '/'
694 698 if scheme != 'file':
695 699 # XXX: why are we quoting the path again with some smart
696 700 # heuristic here? Anyway, it cannot be done with file://
697 701 # urls since path encoding is os/fs dependent (see
698 702 # urllib.pathname2url() for details).
699 703 urlpath = quotepath(urlpath)
700 704 host, port, user, passwd = netlocsplit(netloc)
701 705
702 706 # urllib cannot handle URLs with embedded user or passwd
703 707 url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
704 708 urlpath, query, frag))
705 709 if user:
706 710 netloc = host
707 711 if port:
708 712 netloc += ':' + port
709 713 # Python < 2.4.3 uses only the netloc to search for a password
710 714 authinfo = (None, (url, netloc), user, passwd or '')
711 715 else:
712 716 authinfo = None
713 717 return url, authinfo
714 718
715 719 handlerfuncs = []
716 720
717 721 def opener(ui, authinfo=None):
718 722 '''
719 723 construct an opener suitable for urllib2
720 724 authinfo will be added to the password manager
721 725 '''
722 726 handlers = [httphandler()]
723 727 if has_https:
724 728 handlers.append(httpshandler(ui))
725 729
726 730 handlers.append(proxyhandler(ui))
727 731
728 732 passmgr = passwordmgr(ui)
729 733 if authinfo is not None:
730 734 passmgr.add_password(*authinfo)
731 735 user, passwd = authinfo[2:4]
732 736 ui.debug('http auth: user %s, password %s\n' %
733 737 (user, passwd and '*' * len(passwd) or 'not set'))
734 738
735 739 handlers.extend((httpbasicauthhandler(passmgr),
736 740 httpdigestauthhandler(passmgr)))
737 741 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
738 742 opener = urllib2.build_opener(*handlers)
739 743
740 744 # 1.0 here is the _protocol_ version
741 745 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
742 746 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
743 747 return opener
744 748
745 749 scheme_re = re.compile(r'^([a-zA-Z0-9+-.]+)://')
746 750
747 751 def open(ui, url, data=None):
748 752 scheme = None
749 753 m = scheme_re.search(url)
750 754 if m:
751 755 scheme = m.group(1).lower()
752 756 if not scheme:
753 757 path = util.normpath(os.path.abspath(url))
754 758 url = 'file://' + urllib.pathname2url(path)
755 759 authinfo = None
756 760 else:
757 761 url, authinfo = getauthinfo(url)
758 762 return opener(ui, authinfo).open(url, data)
@@ -1,269 +1,275 b''
1 1 Proper https client requires the built-in ssl from Python 2.6.
2 2
3 3 $ "$TESTDIR/hghave" ssl || exit 80
4 4
5 5 Certificates created with:
6 6 printf '.\n.\n.\n.\n.\nlocalhost\nhg@localhost\n' | \
7 7 openssl req -newkey rsa:512 -keyout priv.pem -nodes -x509 -days 9000 -out pub.pem
8 8 Can be dumped with:
9 9 openssl x509 -in pub.pem -text
10 10
11 11 $ cat << EOT > priv.pem
12 12 > -----BEGIN PRIVATE KEY-----
13 13 > MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEApjCWeYGrIa/Vo7LH
14 14 > aRF8ou0tbgHKE33Use/whCnKEUm34rDaXQd4lxxX6aDWg06n9tiVStAKTgQAHJY8
15 15 > j/xgSwIDAQABAkBxHC6+Qlf0VJXGlb6NL16yEVVTQxqDS6hA9zqu6TZjrr0YMfzc
16 16 > EGNIiZGt7HCBL0zO+cPDg/LeCZc6HQhf0KrhAiEAzlJq4hWWzvguWFIJWSoBeBUG
17 17 > MF1ACazQO7PYE8M0qfECIQDONHHP0SKZzz/ZwBZcAveC5K61f/v9hONFwbeYulzR
18 18 > +wIgc9SvbtgB/5Yzpp//4ZAEnR7oh5SClCvyB+KSx52K3nECICbhQphhoXmI10wy
19 19 > aMTellaq0bpNMHFDziqH9RsqAHhjAiEAgYGxfzkftt5IUUn/iFK89aaIpyrpuaAh
20 20 > HY8gUVkVRVs=
21 21 > -----END PRIVATE KEY-----
22 22 > EOT
23 23
24 24 $ cat << EOT > pub.pem
25 25 > -----BEGIN CERTIFICATE-----
26 26 > MIIBqzCCAVWgAwIBAgIJANAXFFyWjGnRMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNV
27 27 > BAMMCWxvY2FsaG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTEw
28 28 > MTAxNDIwMzAxNFoXDTM1MDYwNTIwMzAxNFowMTESMBAGA1UEAwwJbG9jYWxob3N0
29 29 > MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhvc3QwXDANBgkqhkiG9w0BAQEFAANL
30 30 > ADBIAkEApjCWeYGrIa/Vo7LHaRF8ou0tbgHKE33Use/whCnKEUm34rDaXQd4lxxX
31 31 > 6aDWg06n9tiVStAKTgQAHJY8j/xgSwIDAQABo1AwTjAdBgNVHQ4EFgQUE6sA+amm
32 32 > r24dGX0kpjxOgO45hzQwHwYDVR0jBBgwFoAUE6sA+ammr24dGX0kpjxOgO45hzQw
33 33 > DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAFArvQFiAZJgQczRsbYlG1xl
34 34 > t+truk37w5B3m3Ick1ntRcQrqs+hf0CO1q6Squ144geYaQ8CDirSR92fICELI1c=
35 35 > -----END CERTIFICATE-----
36 36 > EOT
37 37 $ cat priv.pem pub.pem >> server.pem
38 38 $ PRIV=`pwd`/server.pem
39 39
40 40 $ cat << EOT > pub-other.pem
41 41 > -----BEGIN CERTIFICATE-----
42 42 > MIIBqzCCAVWgAwIBAgIJALwZS731c/ORMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNV
43 43 > BAMMCWxvY2FsaG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTEw
44 44 > MTAxNDIwNDUxNloXDTM1MDYwNTIwNDUxNlowMTESMBAGA1UEAwwJbG9jYWxob3N0
45 45 > MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhvc3QwXDANBgkqhkiG9w0BAQEFAANL
46 46 > ADBIAkEAsxsapLbHrqqUKuQBxdpK4G3m2LjtyrTSdpzzzFlecxd5yhNP6AyWrufo
47 47 > K4VMGo2xlu9xOo88nDSUNSKPuD09MwIDAQABo1AwTjAdBgNVHQ4EFgQUoIB1iMhN
48 48 > y868rpQ2qk9dHnU6ebswHwYDVR0jBBgwFoAUoIB1iMhNy868rpQ2qk9dHnU6ebsw
49 49 > DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAJ544f125CsE7J2t55PdFaF6
50 50 > bBlNBb91FCywBgSjhBjf+GG3TNPwrPdc3yqeq+hzJiuInqbOBv9abmMyq8Wsoig=
51 51 > -----END CERTIFICATE-----
52 52 > EOT
53 53
54 54 pub.pem patched with other notBefore / notAfter:
55 55
56 56 $ cat << EOT > pub-not-yet.pem
57 57 > -----BEGIN CERTIFICATE-----
58 58 > MIIBqzCCAVWgAwIBAgIJANAXFFyWjGnRMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNVBAMMCWxvY2Fs
59 59 > aG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTM1MDYwNTIwMzAxNFoXDTM1MDYw
60 60 > NTIwMzAxNFowMTESMBAGA1UEAwwJbG9jYWxob3N0MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhv
61 61 > c3QwXDANBgkqhkiG9w0BAQEFAANLADBIAkEApjCWeYGrIa/Vo7LHaRF8ou0tbgHKE33Use/whCnK
62 62 > EUm34rDaXQd4lxxX6aDWg06n9tiVStAKTgQAHJY8j/xgSwIDAQABo1AwTjAdBgNVHQ4EFgQUE6sA
63 63 > +ammr24dGX0kpjxOgO45hzQwHwYDVR0jBBgwFoAUE6sA+ammr24dGX0kpjxOgO45hzQwDAYDVR0T
64 64 > BAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAJXV41gWnkgC7jcpPpFRSUSZaxyzrXmD1CIqQf0WgVDb
65 65 > /12E0vR2DuZitgzUYtBaofM81aTtc0a2/YsrmqePGm0=
66 66 > -----END CERTIFICATE-----
67 67 > EOT
68 68 $ cat priv.pem pub-not-yet.pem > server-not-yet.pem
69 69
70 70 $ cat << EOT > pub-expired.pem
71 71 > -----BEGIN CERTIFICATE-----
72 72 > MIIBqzCCAVWgAwIBAgIJANAXFFyWjGnRMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNVBAMMCWxvY2Fs
73 73 > aG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTEwMTAxNDIwMzAxNFoXDTEwMTAx
74 74 > NDIwMzAxNFowMTESMBAGA1UEAwwJbG9jYWxob3N0MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhv
75 75 > c3QwXDANBgkqhkiG9w0BAQEFAANLADBIAkEApjCWeYGrIa/Vo7LHaRF8ou0tbgHKE33Use/whCnK
76 76 > EUm34rDaXQd4lxxX6aDWg06n9tiVStAKTgQAHJY8j/xgSwIDAQABo1AwTjAdBgNVHQ4EFgQUE6sA
77 77 > +ammr24dGX0kpjxOgO45hzQwHwYDVR0jBBgwFoAUE6sA+ammr24dGX0kpjxOgO45hzQwDAYDVR0T
78 78 > BAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAJfk57DTRf2nUbYaMSlVAARxMNbFGOjQhAUtY400GhKt
79 79 > 2uiKCNGKXVXD3AHWe13yHc5KttzbHQStE5Nm/DlWBWQ=
80 80 > -----END CERTIFICATE-----
81 81 > EOT
82 82 $ cat priv.pem pub-expired.pem > server-expired.pem
83 83
84 84 $ hg init test
85 85 $ cd test
86 86 $ echo foo>foo
87 87 $ mkdir foo.d foo.d/bAr.hg.d foo.d/baR.d.hg
88 88 $ echo foo>foo.d/foo
89 89 $ echo bar>foo.d/bAr.hg.d/BaR
90 90 $ echo bar>foo.d/baR.d.hg/bAR
91 91 $ hg commit -A -m 1
92 92 adding foo
93 93 adding foo.d/bAr.hg.d/BaR
94 94 adding foo.d/baR.d.hg/bAR
95 95 adding foo.d/foo
96 96 $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV
97 97 $ cat ../hg0.pid >> $DAEMON_PIDS
98 98
99 cacert not found
100
101 $ hg in --config web.cacerts=no-such.pem https://localhost:$HGPORT/
102 abort: could not find web.cacerts: no-such.pem
103 [255]
104
99 105 Test server address cannot be reused
100 106
101 107 $ hg serve -p $HGPORT --certificate=$PRIV 2>&1
102 108 abort: cannot start server at ':$HGPORT': Address already in use
103 109 [255]
104 110 $ cd ..
105 111
106 112 clone via pull
107 113
108 114 $ hg clone https://localhost:$HGPORT/ copy-pull
109 115 warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
110 116 requesting all changes
111 117 adding changesets
112 118 adding manifests
113 119 adding file changes
114 120 added 1 changesets with 4 changes to 4 files
115 121 warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
116 122 updating to branch default
117 123 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
118 124 $ hg verify -R copy-pull
119 125 checking changesets
120 126 checking manifests
121 127 crosschecking files in changesets and manifests
122 128 checking files
123 129 4 files, 1 changesets, 4 total revisions
124 130 $ cd test
125 131 $ echo bar > bar
126 132 $ hg commit -A -d '1 0' -m 2
127 133 adding bar
128 134 $ cd ..
129 135
130 136 pull without cacert
131 137
132 138 $ cd copy-pull
133 139 $ echo '[hooks]' >> .hg/hgrc
134 140 $ echo "changegroup = python '$TESTDIR'/printenv.py changegroup" >> .hg/hgrc
135 141 $ hg pull
136 142 warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
137 143 pulling from https://localhost:$HGPORT/
138 144 searching for changes
139 145 adding changesets
140 146 adding manifests
141 147 adding file changes
142 148 added 1 changesets with 1 changes to 1 files
143 149 changegroup hook: HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_URL=https://localhost:$HGPORT/
144 150 warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
145 151 (run 'hg update' to get a working copy)
146 152 $ cd ..
147 153
148 154 cacert configured in local repo
149 155
150 156 $ cp copy-pull/.hg/hgrc copy-pull/.hg/hgrc.bu
151 157 $ echo "[web]" >> copy-pull/.hg/hgrc
152 158 $ echo "cacerts=`pwd`/pub.pem" >> copy-pull/.hg/hgrc
153 159 $ hg -R copy-pull pull --traceback
154 160 pulling from https://localhost:$HGPORT/
155 161 searching for changes
156 162 no changes found
157 163 $ mv copy-pull/.hg/hgrc.bu copy-pull/.hg/hgrc
158 164
159 165 cacert configured globally, also testing expansion of environment
160 166 variables in the filename
161 167
162 168 $ echo "[web]" >> $HGRCPATH
163 169 $ echo 'cacerts=$P/pub.pem' >> $HGRCPATH
164 170 $ P=`pwd` hg -R copy-pull pull
165 171 pulling from https://localhost:$HGPORT/
166 172 searching for changes
167 173 no changes found
168 174 $ P=`pwd` hg -R copy-pull pull --insecure
169 175 warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
170 176 pulling from https://localhost:$HGPORT/
171 177 searching for changes
172 178 no changes found
173 179
174 180 cacert mismatch
175 181
176 182 $ hg -R copy-pull pull --config web.cacerts=pub.pem https://127.0.0.1:$HGPORT/
177 183 abort: 127.0.0.1 certificate error: certificate is for localhost (use --insecure to connect insecurely)
178 184 [255]
179 185 $ hg -R copy-pull pull --config web.cacerts=pub.pem https://127.0.0.1:$HGPORT/ --insecure
180 186 warning: 127.0.0.1 certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
181 187 pulling from https://127.0.0.1:$HGPORT/
182 188 searching for changes
183 189 no changes found
184 190 $ hg -R copy-pull pull --config web.cacerts=pub-other.pem
185 191 abort: error: *:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (glob)
186 192 [255]
187 193 $ hg -R copy-pull pull --config web.cacerts=pub-other.pem --insecure
188 194 warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
189 195 pulling from https://localhost:$HGPORT/
190 196 searching for changes
191 197 no changes found
192 198
193 199 Test server cert which isn't valid yet
194 200
195 201 $ hg -R test serve -p $HGPORT1 -d --pid-file=hg1.pid --certificate=server-not-yet.pem
196 202 $ cat hg1.pid >> $DAEMON_PIDS
197 203 $ hg -R copy-pull pull --config web.cacerts=pub-not-yet.pem https://localhost:$HGPORT1/
198 204 abort: error: *:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (glob)
199 205 [255]
200 206
201 207 Test server cert which no longer is valid
202 208
203 209 $ hg -R test serve -p $HGPORT2 -d --pid-file=hg2.pid --certificate=server-expired.pem
204 210 $ cat hg2.pid >> $DAEMON_PIDS
205 211 $ hg -R copy-pull pull --config web.cacerts=pub-expired.pem https://localhost:$HGPORT2/
206 212 abort: error: *:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (glob)
207 213 [255]
208 214
209 215 Fingerprints
210 216
211 217 $ echo "[hostfingerprints]" >> copy-pull/.hg/hgrc
212 218 $ echo "localhost = 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca" >> copy-pull/.hg/hgrc
213 219 $ echo "127.0.0.1 = 914f1aff87249c09b6859b88b1906d30756491ca" >> copy-pull/.hg/hgrc
214 220
215 221 - works without cacerts
216 222 $ hg -R copy-pull id https://localhost:$HGPORT/ --config web.cacerts=
217 223 5fed3813f7f5
218 224
219 225 - fails when cert doesn't match hostname (port is ignored)
220 226 $ hg -R copy-pull id https://localhost:$HGPORT1/
221 227 abort: invalid certificate for localhost with fingerprint 28:ff:71:bf:65:31:14:23:ad:62:92:b4:0e:31:99:18:fc:83:e3:9b
222 228 [255]
223 229
224 230 - ignores that certificate doesn't match hostname
225 231 $ hg -R copy-pull id https://127.0.0.1:$HGPORT/
226 232 5fed3813f7f5
227 233
228 234 Prepare for connecting through proxy
229 235
230 236 $ kill `cat hg1.pid`
231 237 $ sleep 1
232 238
233 239 $ ("$TESTDIR/tinyproxy.py" $HGPORT1 localhost >proxy.log 2>&1 </dev/null &
234 240 $ echo $! > proxy.pid)
235 241 $ cat proxy.pid >> $DAEMON_PIDS
236 242 $ sleep 2
237 243
238 244 $ echo "[http_proxy]" >> copy-pull/.hg/hgrc
239 245 $ echo "always=True" >> copy-pull/.hg/hgrc
240 246 $ echo "[hostfingerprints]" >> copy-pull/.hg/hgrc
241 247 $ echo "localhost =" >> copy-pull/.hg/hgrc
242 248
243 249 Test unvalidated https through proxy
244 250
245 251 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --insecure --traceback
246 252 warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
247 253 pulling from https://localhost:$HGPORT/
248 254 searching for changes
249 255 no changes found
250 256
251 257 Test https with cacert and fingerprint through proxy
252 258
253 259 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --config web.cacerts=pub.pem
254 260 pulling from https://localhost:$HGPORT/
255 261 searching for changes
256 262 no changes found
257 263 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull https://127.0.0.1:$HGPORT/
258 264 pulling from https://127.0.0.1:$HGPORT/
259 265 searching for changes
260 266 no changes found
261 267
262 268 Test https with cert problems through proxy
263 269
264 270 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --config web.cacerts=pub-other.pem
265 271 abort: error: *:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (glob)
266 272 [255]
267 273 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --config web.cacerts=pub-expired.pem https://localhost:$HGPORT2/
268 274 abort: error: *:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (glob)
269 275 [255]
General Comments 0
You need to be logged in to leave comments. Login now