##// END OF EJS Templates
merge with stable
Matt Mackall -
r29501:be68a444 merge default
parent child Browse files
Show More
@@ -1,596 +1,596 b''
1 1 # sslutil.py - SSL 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 from __future__ import absolute_import
11 11
12 12 import hashlib
13 13 import os
14 14 import re
15 15 import ssl
16 16 import sys
17 17
18 18 from .i18n import _
19 19 from . import (
20 20 error,
21 21 util,
22 22 )
23 23
24 24 # Python 2.7.9+ overhauled the built-in SSL/TLS features of Python. It added
25 25 # support for TLS 1.1, TLS 1.2, SNI, system CA stores, etc. These features are
26 26 # all exposed via the "ssl" module.
27 27 #
28 28 # Depending on the version of Python being used, SSL/TLS support is either
29 29 # modern/secure or legacy/insecure. Many operations in this module have
30 30 # separate code paths depending on support in Python.
31 31
32 32 hassni = getattr(ssl, 'HAS_SNI', False)
33 33
34 34 try:
35 35 OP_NO_SSLv2 = ssl.OP_NO_SSLv2
36 36 OP_NO_SSLv3 = ssl.OP_NO_SSLv3
37 37 except AttributeError:
38 38 OP_NO_SSLv2 = 0x1000000
39 39 OP_NO_SSLv3 = 0x2000000
40 40
41 41 try:
42 42 # ssl.SSLContext was added in 2.7.9 and presence indicates modern
43 43 # SSL/TLS features are available.
44 44 SSLContext = ssl.SSLContext
45 45 modernssl = True
46 46 _canloaddefaultcerts = util.safehasattr(SSLContext, 'load_default_certs')
47 47 except AttributeError:
48 48 modernssl = False
49 49 _canloaddefaultcerts = False
50 50
51 51 # We implement SSLContext using the interface from the standard library.
52 52 class SSLContext(object):
53 53 # ssl.wrap_socket gained the "ciphers" named argument in 2.7.
54 54 _supportsciphers = sys.version_info >= (2, 7)
55 55
56 56 def __init__(self, protocol):
57 57 # From the public interface of SSLContext
58 58 self.protocol = protocol
59 59 self.check_hostname = False
60 60 self.options = 0
61 61 self.verify_mode = ssl.CERT_NONE
62 62
63 63 # Used by our implementation.
64 64 self._certfile = None
65 65 self._keyfile = None
66 66 self._certpassword = None
67 67 self._cacerts = None
68 68 self._ciphers = None
69 69
70 70 def load_cert_chain(self, certfile, keyfile=None, password=None):
71 71 self._certfile = certfile
72 72 self._keyfile = keyfile
73 73 self._certpassword = password
74 74
75 75 def load_default_certs(self, purpose=None):
76 76 pass
77 77
78 78 def load_verify_locations(self, cafile=None, capath=None, cadata=None):
79 79 if capath:
80 80 raise error.Abort(_('capath not supported'))
81 81 if cadata:
82 82 raise error.Abort(_('cadata not supported'))
83 83
84 84 self._cacerts = cafile
85 85
86 86 def set_ciphers(self, ciphers):
87 87 if not self._supportsciphers:
88 88 raise error.Abort(_('setting ciphers not supported'))
89 89
90 90 self._ciphers = ciphers
91 91
92 92 def wrap_socket(self, socket, server_hostname=None, server_side=False):
93 93 # server_hostname is unique to SSLContext.wrap_socket and is used
94 94 # for SNI in that context. So there's nothing for us to do with it
95 95 # in this legacy code since we don't support SNI.
96 96
97 97 args = {
98 98 'keyfile': self._keyfile,
99 99 'certfile': self._certfile,
100 100 'server_side': server_side,
101 101 'cert_reqs': self.verify_mode,
102 102 'ssl_version': self.protocol,
103 103 'ca_certs': self._cacerts,
104 104 }
105 105
106 106 if self._supportsciphers:
107 107 args['ciphers'] = self._ciphers
108 108
109 109 return ssl.wrap_socket(socket, **args)
110 110
111 111 def _hostsettings(ui, hostname):
112 112 """Obtain security settings for a hostname.
113 113
114 114 Returns a dict of settings relevant to that hostname.
115 115 """
116 116 s = {
117 117 # Whether we should attempt to load default/available CA certs
118 118 # if an explicit ``cafile`` is not defined.
119 119 'allowloaddefaultcerts': True,
120 120 # List of 2-tuple of (hash algorithm, hash).
121 121 'certfingerprints': [],
122 122 # Path to file containing concatenated CA certs. Used by
123 123 # SSLContext.load_verify_locations().
124 124 'cafile': None,
125 125 # Whether certificate verification should be disabled.
126 126 'disablecertverification': False,
127 127 # Whether the legacy [hostfingerprints] section has data for this host.
128 128 'legacyfingerprint': False,
129 129 # ssl.CERT_* constant used by SSLContext.verify_mode.
130 130 'verifymode': None,
131 131 }
132 132
133 133 # Look for fingerprints in [hostsecurity] section. Value is a list
134 134 # of <alg>:<fingerprint> strings.
135 135 fingerprints = ui.configlist('hostsecurity', '%s:fingerprints' % hostname,
136 136 [])
137 137 for fingerprint in fingerprints:
138 138 if not (fingerprint.startswith(('sha1:', 'sha256:', 'sha512:'))):
139 139 raise error.Abort(_('invalid fingerprint for %s: %s') % (
140 140 hostname, fingerprint),
141 141 hint=_('must begin with "sha1:", "sha256:", '
142 142 'or "sha512:"'))
143 143
144 144 alg, fingerprint = fingerprint.split(':', 1)
145 145 fingerprint = fingerprint.replace(':', '').lower()
146 146 s['certfingerprints'].append((alg, fingerprint))
147 147
148 148 # Fingerprints from [hostfingerprints] are always SHA-1.
149 149 for fingerprint in ui.configlist('hostfingerprints', hostname, []):
150 150 fingerprint = fingerprint.replace(':', '').lower()
151 151 s['certfingerprints'].append(('sha1', fingerprint))
152 152 s['legacyfingerprint'] = True
153 153
154 154 # If a host cert fingerprint is defined, it is the only thing that
155 155 # matters. No need to validate CA certs.
156 156 if s['certfingerprints']:
157 157 s['verifymode'] = ssl.CERT_NONE
158 158 s['allowloaddefaultcerts'] = False
159 159
160 160 # If --insecure is used, don't take CAs into consideration.
161 161 elif ui.insecureconnections:
162 162 s['disablecertverification'] = True
163 163 s['verifymode'] = ssl.CERT_NONE
164 164 s['allowloaddefaultcerts'] = False
165 165
166 166 if ui.configbool('devel', 'disableloaddefaultcerts'):
167 167 s['allowloaddefaultcerts'] = False
168 168
169 169 # If both fingerprints and a per-host ca file are specified, issue a warning
170 170 # because users should not be surprised about what security is or isn't
171 171 # being performed.
172 172 cafile = ui.config('hostsecurity', '%s:verifycertsfile' % hostname)
173 173 if s['certfingerprints'] and cafile:
174 174 ui.warn(_('(hostsecurity.%s:verifycertsfile ignored when host '
175 175 'fingerprints defined; using host fingerprints for '
176 176 'verification)\n') % hostname)
177 177
178 178 # Try to hook up CA certificate validation unless something above
179 179 # makes it not necessary.
180 180 if s['verifymode'] is None:
181 181 # Look at per-host ca file first.
182 182 if cafile:
183 183 cafile = util.expandpath(cafile)
184 184 if not os.path.exists(cafile):
185 185 raise error.Abort(_('path specified by %s does not exist: %s') %
186 186 ('hostsecurity.%s:verifycertsfile' % hostname,
187 187 cafile))
188 188 s['cafile'] = cafile
189 189 else:
190 190 # Find global certificates file in config.
191 191 cafile = ui.config('web', 'cacerts')
192 192
193 193 if cafile:
194 194 cafile = util.expandpath(cafile)
195 195 if not os.path.exists(cafile):
196 196 raise error.Abort(_('could not find web.cacerts: %s') %
197 197 cafile)
198 198 elif s['allowloaddefaultcerts']:
199 199 # CAs not defined in config. Try to find system bundles.
200 200 cafile = _defaultcacerts(ui)
201 201 if cafile:
202 202 ui.debug('using %s for CA file\n' % cafile)
203 203
204 204 s['cafile'] = cafile
205 205
206 206 # Require certificate validation if CA certs are being loaded and
207 207 # verification hasn't been disabled above.
208 208 if cafile or (_canloaddefaultcerts and s['allowloaddefaultcerts']):
209 209 s['verifymode'] = ssl.CERT_REQUIRED
210 210 else:
211 211 # At this point we don't have a fingerprint, aren't being
212 212 # explicitly insecure, and can't load CA certs. Connecting
213 213 # is insecure. We allow the connection and abort during
214 214 # validation (once we have the fingerprint to print to the
215 215 # user).
216 216 s['verifymode'] = ssl.CERT_NONE
217 217
218 218 assert s['verifymode'] is not None
219 219
220 220 return s
221 221
222 222 def wrapsocket(sock, keyfile, certfile, ui, serverhostname=None):
223 223 """Add SSL/TLS to a socket.
224 224
225 225 This is a glorified wrapper for ``ssl.wrap_socket()``. It makes sane
226 226 choices based on what security options are available.
227 227
228 228 In addition to the arguments supported by ``ssl.wrap_socket``, we allow
229 229 the following additional arguments:
230 230
231 231 * serverhostname - The expected hostname of the remote server. If the
232 232 server (and client) support SNI, this tells the server which certificate
233 233 to use.
234 234 """
235 235 if not serverhostname:
236 236 raise error.Abort(_('serverhostname argument is required'))
237 237
238 238 settings = _hostsettings(ui, serverhostname)
239 239
240 240 # Despite its name, PROTOCOL_SSLv23 selects the highest protocol
241 241 # that both ends support, including TLS protocols. On legacy stacks,
242 242 # the highest it likely goes in TLS 1.0. On modern stacks, it can
243 243 # support TLS 1.2.
244 244 #
245 245 # The PROTOCOL_TLSv* constants select a specific TLS version
246 246 # only (as opposed to multiple versions). So the method for
247 247 # supporting multiple TLS versions is to use PROTOCOL_SSLv23 and
248 248 # disable protocols via SSLContext.options and OP_NO_* constants.
249 249 # However, SSLContext.options doesn't work unless we have the
250 250 # full/real SSLContext available to us.
251 251 #
252 252 # SSLv2 and SSLv3 are broken. We ban them outright.
253 253 if modernssl:
254 254 protocol = ssl.PROTOCOL_SSLv23
255 255 else:
256 256 protocol = ssl.PROTOCOL_TLSv1
257 257
258 258 # TODO use ssl.create_default_context() on modernssl.
259 259 sslcontext = SSLContext(protocol)
260 260
261 261 # This is a no-op on old Python.
262 262 sslcontext.options |= OP_NO_SSLv2 | OP_NO_SSLv3
263 263
264 264 # This still works on our fake SSLContext.
265 265 sslcontext.verify_mode = settings['verifymode']
266 266
267 267 if certfile is not None:
268 268 def password():
269 269 f = keyfile or certfile
270 270 return ui.getpass(_('passphrase for %s: ') % f, '')
271 271 sslcontext.load_cert_chain(certfile, keyfile, password)
272 272
273 273 if settings['cafile'] is not None:
274 274 try:
275 275 sslcontext.load_verify_locations(cafile=settings['cafile'])
276 276 except ssl.SSLError as e:
277 277 raise error.Abort(_('error loading CA file %s: %s') % (
278 278 settings['cafile'], e.args[1]),
279 279 hint=_('file is empty or malformed?'))
280 280 caloaded = True
281 281 elif settings['allowloaddefaultcerts']:
282 282 # This is a no-op on old Python.
283 283 sslcontext.load_default_certs()
284 284 caloaded = True
285 285 else:
286 286 caloaded = False
287 287
288 288 try:
289 289 sslsocket = sslcontext.wrap_socket(sock, server_hostname=serverhostname)
290 290 except ssl.SSLError:
291 291 # If we're doing certificate verification and no CA certs are loaded,
292 292 # that is almost certainly the reason why verification failed. Provide
293 293 # a hint to the user.
294 294 # Only modern ssl module exposes SSLContext.get_ca_certs() so we can
295 295 # only show this warning if modern ssl is available.
296 296 if (caloaded and settings['verifymode'] == ssl.CERT_REQUIRED and
297 297 modernssl and not sslcontext.get_ca_certs()):
298 298 ui.warn(_('(an attempt was made to load CA certificates but none '
299 299 'were loaded; see '
300 300 'https://mercurial-scm.org/wiki/SecureConnections for '
301 301 'how to configure Mercurial to avoid this error)\n'))
302 302 raise
303 303
304 304 # check if wrap_socket failed silently because socket had been
305 305 # closed
306 306 # - see http://bugs.python.org/issue13721
307 307 if not sslsocket.cipher():
308 308 raise error.Abort(_('ssl connection failed'))
309 309
310 310 sslsocket._hgstate = {
311 311 'caloaded': caloaded,
312 312 'hostname': serverhostname,
313 313 'settings': settings,
314 314 'ui': ui,
315 315 }
316 316
317 317 return sslsocket
318 318
319 319 class wildcarderror(Exception):
320 320 """Represents an error parsing wildcards in DNS name."""
321 321
322 322 def _dnsnamematch(dn, hostname, maxwildcards=1):
323 323 """Match DNS names according RFC 6125 section 6.4.3.
324 324
325 325 This code is effectively copied from CPython's ssl._dnsname_match.
326 326
327 327 Returns a bool indicating whether the expected hostname matches
328 328 the value in ``dn``.
329 329 """
330 330 pats = []
331 331 if not dn:
332 332 return False
333 333
334 334 pieces = dn.split(r'.')
335 335 leftmost = pieces[0]
336 336 remainder = pieces[1:]
337 337 wildcards = leftmost.count('*')
338 338 if wildcards > maxwildcards:
339 339 raise wildcarderror(
340 340 _('too many wildcards in certificate DNS name: %s') % dn)
341 341
342 342 # speed up common case w/o wildcards
343 343 if not wildcards:
344 344 return dn.lower() == hostname.lower()
345 345
346 346 # RFC 6125, section 6.4.3, subitem 1.
347 347 # The client SHOULD NOT attempt to match a presented identifier in which
348 348 # the wildcard character comprises a label other than the left-most label.
349 349 if leftmost == '*':
350 350 # When '*' is a fragment by itself, it matches a non-empty dotless
351 351 # fragment.
352 352 pats.append('[^.]+')
353 353 elif leftmost.startswith('xn--') or hostname.startswith('xn--'):
354 354 # RFC 6125, section 6.4.3, subitem 3.
355 355 # The client SHOULD NOT attempt to match a presented identifier
356 356 # where the wildcard character is embedded within an A-label or
357 357 # U-label of an internationalized domain name.
358 358 pats.append(re.escape(leftmost))
359 359 else:
360 360 # Otherwise, '*' matches any dotless string, e.g. www*
361 361 pats.append(re.escape(leftmost).replace(r'\*', '[^.]*'))
362 362
363 363 # add the remaining fragments, ignore any wildcards
364 364 for frag in remainder:
365 365 pats.append(re.escape(frag))
366 366
367 367 pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)
368 368 return pat.match(hostname) is not None
369 369
370 370 def _verifycert(cert, hostname):
371 371 '''Verify that cert (in socket.getpeercert() format) matches hostname.
372 372 CRLs is not handled.
373 373
374 374 Returns error message if any problems are found and None on success.
375 375 '''
376 376 if not cert:
377 377 return _('no certificate received')
378 378
379 379 dnsnames = []
380 380 san = cert.get('subjectAltName', [])
381 381 for key, value in san:
382 382 if key == 'DNS':
383 383 try:
384 384 if _dnsnamematch(value, hostname):
385 385 return
386 386 except wildcarderror as e:
387 return e.message
387 return e.args[0]
388 388
389 389 dnsnames.append(value)
390 390
391 391 if not dnsnames:
392 392 # The subject is only checked when there is no DNS in subjectAltName.
393 393 for sub in cert.get('subject', []):
394 394 for key, value in sub:
395 395 # According to RFC 2818 the most specific Common Name must
396 396 # be used.
397 397 if key == 'commonName':
398 398 # 'subject' entries are unicide.
399 399 try:
400 400 value = value.encode('ascii')
401 401 except UnicodeEncodeError:
402 402 return _('IDN in certificate not supported')
403 403
404 404 try:
405 405 if _dnsnamematch(value, hostname):
406 406 return
407 407 except wildcarderror as e:
408 return e.message
408 return e.args[0]
409 409
410 410 dnsnames.append(value)
411 411
412 412 if len(dnsnames) > 1:
413 413 return _('certificate is for %s') % ', '.join(dnsnames)
414 414 elif len(dnsnames) == 1:
415 415 return _('certificate is for %s') % dnsnames[0]
416 416 else:
417 417 return _('no commonName or subjectAltName found in certificate')
418 418
419 419 def _plainapplepython():
420 420 """return true if this seems to be a pure Apple Python that
421 421 * is unfrozen and presumably has the whole mercurial module in the file
422 422 system
423 423 * presumably is an Apple Python that uses Apple OpenSSL which has patches
424 424 for using system certificate store CAs in addition to the provided
425 425 cacerts file
426 426 """
427 427 if sys.platform != 'darwin' or util.mainfrozen() or not sys.executable:
428 428 return False
429 429 exe = os.path.realpath(sys.executable).lower()
430 430 return (exe.startswith('/usr/bin/python') or
431 431 exe.startswith('/system/library/frameworks/python.framework/'))
432 432
433 433 _systemcacertpaths = [
434 434 # RHEL, CentOS, and Fedora
435 435 '/etc/pki/tls/certs/ca-bundle.trust.crt',
436 436 # Debian, Ubuntu, Gentoo
437 437 '/etc/ssl/certs/ca-certificates.crt',
438 438 ]
439 439
440 440 def _defaultcacerts(ui):
441 441 """return path to default CA certificates or None.
442 442
443 443 It is assumed this function is called when the returned certificates
444 444 file will actually be used to validate connections. Therefore this
445 445 function may print warnings or debug messages assuming this usage.
446 446
447 447 We don't print a message when the Python is able to load default
448 448 CA certs because this scenario is detected at socket connect time.
449 449 """
450 450 # The "certifi" Python package provides certificates. If it is installed,
451 451 # assume the user intends it to be used and use it.
452 452 try:
453 453 import certifi
454 454 certs = certifi.where()
455 455 ui.debug('using ca certificates from certifi\n')
456 456 return certs
457 457 except ImportError:
458 458 pass
459 459
460 460 # On Windows, only the modern ssl module is capable of loading the system
461 461 # CA certificates. If we're not capable of doing that, emit a warning
462 462 # because we'll get a certificate verification error later and the lack
463 463 # of loaded CA certificates will be the reason why.
464 464 # Assertion: this code is only called if certificates are being verified.
465 465 if os.name == 'nt':
466 466 if not _canloaddefaultcerts:
467 467 ui.warn(_('(unable to load Windows CA certificates; see '
468 468 'https://mercurial-scm.org/wiki/SecureConnections for '
469 469 'how to configure Mercurial to avoid this message)\n'))
470 470
471 471 return None
472 472
473 473 # Apple's OpenSSL has patches that allow a specially constructed certificate
474 474 # to load the system CA store. If we're running on Apple Python, use this
475 475 # trick.
476 476 if _plainapplepython():
477 477 dummycert = os.path.join(os.path.dirname(__file__), 'dummycert.pem')
478 478 if os.path.exists(dummycert):
479 479 return dummycert
480 480
481 481 # The Apple OpenSSL trick isn't available to us. If Python isn't able to
482 482 # load system certs, we're out of luck.
483 483 if sys.platform == 'darwin':
484 484 # FUTURE Consider looking for Homebrew or MacPorts installed certs
485 485 # files. Also consider exporting the keychain certs to a file during
486 486 # Mercurial install.
487 487 if not _canloaddefaultcerts:
488 488 ui.warn(_('(unable to load CA certificates; see '
489 489 'https://mercurial-scm.org/wiki/SecureConnections for '
490 490 'how to configure Mercurial to avoid this message)\n'))
491 491 return None
492 492
493 493 # Try to find CA certificates in well-known locations. We print a warning
494 494 # when using a found file because we don't want too much silent magic
495 495 # for security settings. The expectation is that proper Mercurial
496 496 # installs will have the CA certs path defined at install time and the
497 497 # installer/packager will make an appropriate decision on the user's
498 498 # behalf. We only get here and perform this setting as a feature of
499 499 # last resort.
500 500 if not _canloaddefaultcerts:
501 501 for path in _systemcacertpaths:
502 502 if os.path.isfile(path):
503 503 ui.warn(_('(using CA certificates from %s; if you see this '
504 504 'message, your Mercurial install is not properly '
505 505 'configured; see '
506 506 'https://mercurial-scm.org/wiki/SecureConnections '
507 507 'for how to configure Mercurial to avoid this '
508 508 'message)\n') % path)
509 509 return path
510 510
511 511 ui.warn(_('(unable to load CA certificates; see '
512 512 'https://mercurial-scm.org/wiki/SecureConnections for '
513 513 'how to configure Mercurial to avoid this message)\n'))
514 514
515 515 return None
516 516
517 517 def validatesocket(sock):
518 518 """Validate a socket meets security requiremnets.
519 519
520 520 The passed socket must have been created with ``wrapsocket()``.
521 521 """
522 522 host = sock._hgstate['hostname']
523 523 ui = sock._hgstate['ui']
524 524 settings = sock._hgstate['settings']
525 525
526 526 try:
527 527 peercert = sock.getpeercert(True)
528 528 peercert2 = sock.getpeercert()
529 529 except AttributeError:
530 530 raise error.Abort(_('%s ssl connection error') % host)
531 531
532 532 if not peercert:
533 533 raise error.Abort(_('%s certificate error: '
534 534 'no certificate received') % host)
535 535
536 536 if settings['disablecertverification']:
537 537 # We don't print the certificate fingerprint because it shouldn't
538 538 # be necessary: if the user requested certificate verification be
539 539 # disabled, they presumably already saw a message about the inability
540 540 # to verify the certificate and this message would have printed the
541 541 # fingerprint. So printing the fingerprint here adds little to no
542 542 # value.
543 543 ui.warn(_('warning: connection security to %s is disabled per current '
544 544 'settings; communication is susceptible to eavesdropping '
545 545 'and tampering\n') % host)
546 546 return
547 547
548 548 # If a certificate fingerprint is pinned, use it and only it to
549 549 # validate the remote cert.
550 550 peerfingerprints = {
551 551 'sha1': hashlib.sha1(peercert).hexdigest(),
552 552 'sha256': hashlib.sha256(peercert).hexdigest(),
553 553 'sha512': hashlib.sha512(peercert).hexdigest(),
554 554 }
555 555
556 556 def fmtfingerprint(s):
557 557 return ':'.join([s[x:x + 2] for x in range(0, len(s), 2)])
558 558
559 559 nicefingerprint = 'sha256:%s' % fmtfingerprint(peerfingerprints['sha256'])
560 560
561 561 if settings['certfingerprints']:
562 562 for hash, fingerprint in settings['certfingerprints']:
563 563 if peerfingerprints[hash].lower() == fingerprint:
564 564 ui.debug('%s certificate matched fingerprint %s:%s\n' %
565 565 (host, hash, fmtfingerprint(fingerprint)))
566 566 return
567 567
568 568 # Pinned fingerprint didn't match. This is a fatal error.
569 569 if settings['legacyfingerprint']:
570 570 section = 'hostfingerprint'
571 571 nice = fmtfingerprint(peerfingerprints['sha1'])
572 572 else:
573 573 section = 'hostsecurity'
574 574 nice = '%s:%s' % (hash, fmtfingerprint(peerfingerprints[hash]))
575 575 raise error.Abort(_('certificate for %s has unexpected '
576 576 'fingerprint %s') % (host, nice),
577 577 hint=_('check %s configuration') % section)
578 578
579 579 # Security is enabled but no CAs are loaded. We can't establish trust
580 580 # for the cert so abort.
581 581 if not sock._hgstate['caloaded']:
582 582 raise error.Abort(
583 583 _('unable to verify security of %s (no loaded CA certificates); '
584 584 'refusing to connect') % host,
585 585 hint=_('see https://mercurial-scm.org/wiki/SecureConnections for '
586 586 'how to configure Mercurial to avoid this error or set '
587 587 'hostsecurity.%s:fingerprints=%s to trust this server') %
588 588 (host, nicefingerprint))
589 589
590 590 msg = _verifycert(peercert2, host)
591 591 if msg:
592 592 raise error.Abort(_('%s certificate error: %s') % (host, msg),
593 593 hint=_('set hostsecurity.%s:certfingerprints=%s '
594 594 'config setting or use --insecure to connect '
595 595 'insecurely') %
596 596 (host, nicefingerprint))
General Comments 0
You need to be logged in to leave comments. Login now