##// END OF EJS Templates
sslutil: try to find CA certficates in well-known locations...
Gregory Szorc -
r29500:4b16a5bd default
parent child Browse files
Show More
@@ -1,564 +1,596
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 387 return e.message
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 408 return e.message
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 _systemcacertpaths = [
434 # RHEL, CentOS, and Fedora
435 '/etc/pki/tls/certs/ca-bundle.trust.crt',
436 # Debian, Ubuntu, Gentoo
437 '/etc/ssl/certs/ca-certificates.crt',
438 ]
439
433 440 def _defaultcacerts(ui):
434 441 """return path to default CA certificates or None.
435 442
436 443 It is assumed this function is called when the returned certificates
437 444 file will actually be used to validate connections. Therefore this
438 445 function may print warnings or debug messages assuming this usage.
446
447 We don't print a message when the Python is able to load default
448 CA certs because this scenario is detected at socket connect time.
439 449 """
440 450 # The "certifi" Python package provides certificates. If it is installed,
441 451 # assume the user intends it to be used and use it.
442 452 try:
443 453 import certifi
444 454 certs = certifi.where()
445 455 ui.debug('using ca certificates from certifi\n')
446 456 return certs
447 457 except ImportError:
448 458 pass
449 459
450 460 # On Windows, only the modern ssl module is capable of loading the system
451 461 # CA certificates. If we're not capable of doing that, emit a warning
452 462 # because we'll get a certificate verification error later and the lack
453 463 # of loaded CA certificates will be the reason why.
454 464 # Assertion: this code is only called if certificates are being verified.
455 465 if os.name == 'nt':
456 466 if not _canloaddefaultcerts:
457 467 ui.warn(_('(unable to load Windows CA certificates; see '
458 468 'https://mercurial-scm.org/wiki/SecureConnections for '
459 469 'how to configure Mercurial to avoid this message)\n'))
460 470
461 471 return None
462 472
463 473 # Apple's OpenSSL has patches that allow a specially constructed certificate
464 474 # to load the system CA store. If we're running on Apple Python, use this
465 475 # trick.
466 476 if _plainapplepython():
467 477 dummycert = os.path.join(os.path.dirname(__file__), 'dummycert.pem')
468 478 if os.path.exists(dummycert):
469 479 return dummycert
470 480
471 481 # The Apple OpenSSL trick isn't available to us. If Python isn't able to
472 482 # load system certs, we're out of luck.
473 483 if sys.platform == 'darwin':
474 484 # FUTURE Consider looking for Homebrew or MacPorts installed certs
475 485 # files. Also consider exporting the keychain certs to a file during
476 486 # Mercurial install.
477 487 if not _canloaddefaultcerts:
478 488 ui.warn(_('(unable to load CA certificates; see '
479 489 'https://mercurial-scm.org/wiki/SecureConnections for '
480 490 'how to configure Mercurial to avoid this message)\n'))
481 491 return None
482 492
493 # Try to find CA certificates in well-known locations. We print a warning
494 # when using a found file because we don't want too much silent magic
495 # for security settings. The expectation is that proper Mercurial
496 # installs will have the CA certs path defined at install time and the
497 # installer/packager will make an appropriate decision on the user's
498 # behalf. We only get here and perform this setting as a feature of
499 # last resort.
500 if not _canloaddefaultcerts:
501 for path in _systemcacertpaths:
502 if os.path.isfile(path):
503 ui.warn(_('(using CA certificates from %s; if you see this '
504 'message, your Mercurial install is not properly '
505 'configured; see '
506 'https://mercurial-scm.org/wiki/SecureConnections '
507 'for how to configure Mercurial to avoid this '
508 'message)\n') % path)
509 return path
510
511 ui.warn(_('(unable to load CA certificates; see '
512 'https://mercurial-scm.org/wiki/SecureConnections for '
513 'how to configure Mercurial to avoid this message)\n'))
514
483 515 return None
484 516
485 517 def validatesocket(sock):
486 518 """Validate a socket meets security requiremnets.
487 519
488 520 The passed socket must have been created with ``wrapsocket()``.
489 521 """
490 522 host = sock._hgstate['hostname']
491 523 ui = sock._hgstate['ui']
492 524 settings = sock._hgstate['settings']
493 525
494 526 try:
495 527 peercert = sock.getpeercert(True)
496 528 peercert2 = sock.getpeercert()
497 529 except AttributeError:
498 530 raise error.Abort(_('%s ssl connection error') % host)
499 531
500 532 if not peercert:
501 533 raise error.Abort(_('%s certificate error: '
502 534 'no certificate received') % host)
503 535
504 536 if settings['disablecertverification']:
505 537 # We don't print the certificate fingerprint because it shouldn't
506 538 # be necessary: if the user requested certificate verification be
507 539 # disabled, they presumably already saw a message about the inability
508 540 # to verify the certificate and this message would have printed the
509 541 # fingerprint. So printing the fingerprint here adds little to no
510 542 # value.
511 543 ui.warn(_('warning: connection security to %s is disabled per current '
512 544 'settings; communication is susceptible to eavesdropping '
513 545 'and tampering\n') % host)
514 546 return
515 547
516 548 # If a certificate fingerprint is pinned, use it and only it to
517 549 # validate the remote cert.
518 550 peerfingerprints = {
519 551 'sha1': hashlib.sha1(peercert).hexdigest(),
520 552 'sha256': hashlib.sha256(peercert).hexdigest(),
521 553 'sha512': hashlib.sha512(peercert).hexdigest(),
522 554 }
523 555
524 556 def fmtfingerprint(s):
525 557 return ':'.join([s[x:x + 2] for x in range(0, len(s), 2)])
526 558
527 559 nicefingerprint = 'sha256:%s' % fmtfingerprint(peerfingerprints['sha256'])
528 560
529 561 if settings['certfingerprints']:
530 562 for hash, fingerprint in settings['certfingerprints']:
531 563 if peerfingerprints[hash].lower() == fingerprint:
532 564 ui.debug('%s certificate matched fingerprint %s:%s\n' %
533 565 (host, hash, fmtfingerprint(fingerprint)))
534 566 return
535 567
536 568 # Pinned fingerprint didn't match. This is a fatal error.
537 569 if settings['legacyfingerprint']:
538 570 section = 'hostfingerprint'
539 571 nice = fmtfingerprint(peerfingerprints['sha1'])
540 572 else:
541 573 section = 'hostsecurity'
542 574 nice = '%s:%s' % (hash, fmtfingerprint(peerfingerprints[hash]))
543 575 raise error.Abort(_('certificate for %s has unexpected '
544 576 'fingerprint %s') % (host, nice),
545 577 hint=_('check %s configuration') % section)
546 578
547 579 # Security is enabled but no CAs are loaded. We can't establish trust
548 580 # for the cert so abort.
549 581 if not sock._hgstate['caloaded']:
550 582 raise error.Abort(
551 583 _('unable to verify security of %s (no loaded CA certificates); '
552 584 'refusing to connect') % host,
553 585 hint=_('see https://mercurial-scm.org/wiki/SecureConnections for '
554 586 'how to configure Mercurial to avoid this error or set '
555 587 'hostsecurity.%s:fingerprints=%s to trust this server') %
556 588 (host, nicefingerprint))
557 589
558 590 msg = _verifycert(peercert2, host)
559 591 if msg:
560 592 raise error.Abort(_('%s certificate error: %s') % (host, msg),
561 593 hint=_('set hostsecurity.%s:certfingerprints=%s '
562 594 'config setting or use --insecure to connect '
563 595 'insecurely') %
564 596 (host, nicefingerprint))
@@ -1,448 +1,450
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 Make server certificates:
6 6
7 7 $ CERTSDIR="$TESTDIR/sslcerts"
8 8 $ cat "$CERTSDIR/priv.pem" "$CERTSDIR/pub.pem" >> server.pem
9 9 $ PRIV=`pwd`/server.pem
10 10 $ cat "$CERTSDIR/priv.pem" "$CERTSDIR/pub-not-yet.pem" > server-not-yet.pem
11 11 $ cat "$CERTSDIR/priv.pem" "$CERTSDIR/pub-expired.pem" > server-expired.pem
12 12
13 13 $ hg init test
14 14 $ cd test
15 15 $ echo foo>foo
16 16 $ mkdir foo.d foo.d/bAr.hg.d foo.d/baR.d.hg
17 17 $ echo foo>foo.d/foo
18 18 $ echo bar>foo.d/bAr.hg.d/BaR
19 19 $ echo bar>foo.d/baR.d.hg/bAR
20 20 $ hg commit -A -m 1
21 21 adding foo
22 22 adding foo.d/bAr.hg.d/BaR
23 23 adding foo.d/baR.d.hg/bAR
24 24 adding foo.d/foo
25 25 $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV
26 26 $ cat ../hg0.pid >> $DAEMON_PIDS
27 27
28 28 cacert not found
29 29
30 30 $ hg in --config web.cacerts=no-such.pem https://localhost:$HGPORT/
31 31 abort: could not find web.cacerts: no-such.pem
32 32 [255]
33 33
34 34 Test server address cannot be reused
35 35
36 36 #if windows
37 37 $ hg serve -p $HGPORT --certificate=$PRIV 2>&1
38 38 abort: cannot start server at ':$HGPORT':
39 39 [255]
40 40 #else
41 41 $ hg serve -p $HGPORT --certificate=$PRIV 2>&1
42 42 abort: cannot start server at ':$HGPORT': Address already in use
43 43 [255]
44 44 #endif
45 45 $ cd ..
46 46
47 47 Our test cert is not signed by a trusted CA. It should fail to verify if
48 48 we are able to load CA certs.
49 49
50 50 #if sslcontext defaultcacerts no-defaultcacertsloaded
51 51 $ hg clone https://localhost:$HGPORT/ copy-pull
52 52 (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)
53 53 abort: error: *certificate verify failed* (glob)
54 54 [255]
55 55 #endif
56 56
57 57 #if no-sslcontext defaultcacerts
58 58 $ hg clone https://localhost:$HGPORT/ copy-pull
59 (using CA certificates from *; if you see this message, your Mercurial install is not properly configured; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message) (glob) (?)
59 60 abort: error: *certificate verify failed* (glob)
60 61 [255]
61 62 #endif
62 63
63 64 #if no-sslcontext windows
64 65 $ hg clone https://localhost:$HGPORT/ copy-pull
65 66 (unable to load Windows CA certificates; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message)
66 67 abort: error: *certificate verify failed* (glob)
67 68 [255]
68 69 #endif
69 70
70 71 #if no-sslcontext osx
71 72 $ hg clone https://localhost:$HGPORT/ copy-pull
72 73 (unable to load CA certificates; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message)
73 74 abort: localhost certificate error: no certificate received
74 75 (set hostsecurity.localhost:certfingerprints=sha256:62:09:97:2f:97:60:e3:65:8f:12:5d:78:9e:35:a1:36:7a:65:4b:0e:9f:ac:db:c3:bc:6e:b6:a3:c0:16:e0:30 config setting or use --insecure to connect insecurely)
75 76 [255]
76 77 #endif
77 78
78 79 #if defaultcacertsloaded
79 80 $ hg clone https://localhost:$HGPORT/ copy-pull
81 (using CA certificates from *; if you see this message, your Mercurial install is not properly configured; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message) (glob) (?)
80 82 abort: error: *certificate verify failed* (glob)
81 83 [255]
82 84 #endif
83 85
84 86 #if no-defaultcacerts
85 87 $ hg clone https://localhost:$HGPORT/ copy-pull
86 88 (unable to load * certificates; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message) (glob) (?)
87 89 abort: localhost certificate error: no certificate received
88 90 (set hostsecurity.localhost:certfingerprints=sha256:62:09:97:2f:97:60:e3:65:8f:12:5d:78:9e:35:a1:36:7a:65:4b:0e:9f:ac:db:c3:bc:6e:b6:a3:c0:16:e0:30 config setting or use --insecure to connect insecurely)
89 91 [255]
90 92 #endif
91 93
92 94 Specifying a per-host certificate file that doesn't exist will abort
93 95
94 96 $ hg --config hostsecurity.localhost:verifycertsfile=/does/not/exist clone https://localhost:$HGPORT/
95 97 abort: path specified by hostsecurity.localhost:verifycertsfile does not exist: /does/not/exist
96 98 [255]
97 99
98 100 A malformed per-host certificate file will raise an error
99 101
100 102 $ echo baddata > badca.pem
101 103 #if sslcontext
102 104 $ hg --config hostsecurity.localhost:verifycertsfile=badca.pem clone https://localhost:$HGPORT/
103 105 abort: error loading CA file badca.pem: * (glob)
104 106 (file is empty or malformed?)
105 107 [255]
106 108 #else
107 109 $ hg --config hostsecurity.localhost:verifycertsfile=badca.pem clone https://localhost:$HGPORT/
108 110 abort: error: * (glob)
109 111 [255]
110 112 #endif
111 113
112 114 A per-host certificate mismatching the server will fail verification
113 115
114 116 (modern ssl is able to discern whether the loaded cert is a CA cert)
115 117 #if sslcontext
116 118 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/client-cert.pem" clone https://localhost:$HGPORT/
117 119 (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)
118 120 abort: error: *certificate verify failed* (glob)
119 121 [255]
120 122 #else
121 123 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/client-cert.pem" clone https://localhost:$HGPORT/
122 124 abort: error: *certificate verify failed* (glob)
123 125 [255]
124 126 #endif
125 127
126 128 A per-host certificate matching the server's cert will be accepted
127 129
128 130 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/pub.pem" clone -U https://localhost:$HGPORT/ perhostgood1
129 131 requesting all changes
130 132 adding changesets
131 133 adding manifests
132 134 adding file changes
133 135 added 1 changesets with 4 changes to 4 files
134 136
135 137 A per-host certificate with multiple certs and one matching will be accepted
136 138
137 139 $ cat "$CERTSDIR/client-cert.pem" "$CERTSDIR/pub.pem" > perhost.pem
138 140 $ hg --config hostsecurity.localhost:verifycertsfile=perhost.pem clone -U https://localhost:$HGPORT/ perhostgood2
139 141 requesting all changes
140 142 adding changesets
141 143 adding manifests
142 144 adding file changes
143 145 added 1 changesets with 4 changes to 4 files
144 146
145 147 Defining both per-host certificate and a fingerprint will print a warning
146 148
147 149 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/pub.pem" --config hostsecurity.localhost:fingerprints=sha1:914f1aff87249c09b6859b88b1906d30756491ca clone -U https://localhost:$HGPORT/ caandfingerwarning
148 150 (hostsecurity.localhost:verifycertsfile ignored when host fingerprints defined; using host fingerprints for verification)
149 151 requesting all changes
150 152 adding changesets
151 153 adding manifests
152 154 adding file changes
153 155 added 1 changesets with 4 changes to 4 files
154 156
155 157 $ DISABLECACERTS="--config devel.disableloaddefaultcerts=true"
156 158
157 159 Inability to verify peer certificate will result in abort
158 160
159 161 $ hg clone https://localhost:$HGPORT/ copy-pull $DISABLECACERTS
160 162 abort: unable to verify security of localhost (no loaded CA certificates); refusing to connect
161 163 (see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error or set hostsecurity.localhost:fingerprints=sha256:62:09:97:2f:97:60:e3:65:8f:12:5d:78:9e:35:a1:36:7a:65:4b:0e:9f:ac:db:c3:bc:6e:b6:a3:c0:16:e0:30 to trust this server)
162 164 [255]
163 165
164 166 $ hg clone --insecure https://localhost:$HGPORT/ copy-pull
165 167 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
166 168 requesting all changes
167 169 adding changesets
168 170 adding manifests
169 171 adding file changes
170 172 added 1 changesets with 4 changes to 4 files
171 173 updating to branch default
172 174 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
173 175 $ hg verify -R copy-pull
174 176 checking changesets
175 177 checking manifests
176 178 crosschecking files in changesets and manifests
177 179 checking files
178 180 4 files, 1 changesets, 4 total revisions
179 181 $ cd test
180 182 $ echo bar > bar
181 183 $ hg commit -A -d '1 0' -m 2
182 184 adding bar
183 185 $ cd ..
184 186
185 187 pull without cacert
186 188
187 189 $ cd copy-pull
188 190 $ echo '[hooks]' >> .hg/hgrc
189 191 $ echo "changegroup = printenv.py changegroup" >> .hg/hgrc
190 192 $ hg pull $DISABLECACERTS
191 193 pulling from https://localhost:$HGPORT/
192 194 abort: unable to verify security of localhost (no loaded CA certificates); refusing to connect
193 195 (see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error or set hostsecurity.localhost:fingerprints=sha256:62:09:97:2f:97:60:e3:65:8f:12:5d:78:9e:35:a1:36:7a:65:4b:0e:9f:ac:db:c3:bc:6e:b6:a3:c0:16:e0:30 to trust this server)
194 196 [255]
195 197
196 198 $ hg pull --insecure
197 199 pulling from https://localhost:$HGPORT/
198 200 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
199 201 searching for changes
200 202 adding changesets
201 203 adding manifests
202 204 adding file changes
203 205 added 1 changesets with 1 changes to 1 files
204 206 changegroup hook: HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_NODE_LAST=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=https://localhost:$HGPORT/ (glob)
205 207 (run 'hg update' to get a working copy)
206 208 $ cd ..
207 209
208 210 cacert configured in local repo
209 211
210 212 $ cp copy-pull/.hg/hgrc copy-pull/.hg/hgrc.bu
211 213 $ echo "[web]" >> copy-pull/.hg/hgrc
212 214 $ echo "cacerts=$CERTSDIR/pub.pem" >> copy-pull/.hg/hgrc
213 215 $ hg -R copy-pull pull --traceback
214 216 pulling from https://localhost:$HGPORT/
215 217 searching for changes
216 218 no changes found
217 219 $ mv copy-pull/.hg/hgrc.bu copy-pull/.hg/hgrc
218 220
219 221 cacert configured globally, also testing expansion of environment
220 222 variables in the filename
221 223
222 224 $ echo "[web]" >> $HGRCPATH
223 225 $ echo 'cacerts=$P/pub.pem' >> $HGRCPATH
224 226 $ P="$CERTSDIR" hg -R copy-pull pull
225 227 pulling from https://localhost:$HGPORT/
226 228 searching for changes
227 229 no changes found
228 230 $ P="$CERTSDIR" hg -R copy-pull pull --insecure
229 231 pulling from https://localhost:$HGPORT/
230 232 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
231 233 searching for changes
232 234 no changes found
233 235
234 236 empty cacert file
235 237
236 238 $ touch emptycafile
237 239
238 240 #if sslcontext
239 241 $ hg --config web.cacerts=emptycafile -R copy-pull pull
240 242 pulling from https://localhost:$HGPORT/
241 243 abort: error loading CA file emptycafile: * (glob)
242 244 (file is empty or malformed?)
243 245 [255]
244 246 #else
245 247 $ hg --config web.cacerts=emptycafile -R copy-pull pull
246 248 pulling from https://localhost:$HGPORT/
247 249 abort: error: * (glob)
248 250 [255]
249 251 #endif
250 252
251 253 cacert mismatch
252 254
253 255 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub.pem" \
254 256 > https://127.0.0.1:$HGPORT/
255 257 pulling from https://127.0.0.1:$HGPORT/
256 258 abort: 127.0.0.1 certificate error: certificate is for localhost
257 259 (set hostsecurity.127.0.0.1:certfingerprints=sha256:62:09:97:2f:97:60:e3:65:8f:12:5d:78:9e:35:a1:36:7a:65:4b:0e:9f:ac:db:c3:bc:6e:b6:a3:c0:16:e0:30 config setting or use --insecure to connect insecurely)
258 260 [255]
259 261 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub.pem" \
260 262 > https://127.0.0.1:$HGPORT/ --insecure
261 263 pulling from https://127.0.0.1:$HGPORT/
262 264 warning: connection security to 127.0.0.1 is disabled per current settings; communication is susceptible to eavesdropping and tampering
263 265 searching for changes
264 266 no changes found
265 267 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-other.pem"
266 268 pulling from https://localhost:$HGPORT/
267 269 abort: error: *certificate verify failed* (glob)
268 270 [255]
269 271 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-other.pem" \
270 272 > --insecure
271 273 pulling from https://localhost:$HGPORT/
272 274 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
273 275 searching for changes
274 276 no changes found
275 277
276 278 Test server cert which isn't valid yet
277 279
278 280 $ hg serve -R test -p $HGPORT1 -d --pid-file=hg1.pid --certificate=server-not-yet.pem
279 281 $ cat hg1.pid >> $DAEMON_PIDS
280 282 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-not-yet.pem" \
281 283 > https://localhost:$HGPORT1/
282 284 pulling from https://localhost:$HGPORT1/
283 285 abort: error: *certificate verify failed* (glob)
284 286 [255]
285 287
286 288 Test server cert which no longer is valid
287 289
288 290 $ hg serve -R test -p $HGPORT2 -d --pid-file=hg2.pid --certificate=server-expired.pem
289 291 $ cat hg2.pid >> $DAEMON_PIDS
290 292 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-expired.pem" \
291 293 > https://localhost:$HGPORT2/
292 294 pulling from https://localhost:$HGPORT2/
293 295 abort: error: *certificate verify failed* (glob)
294 296 [255]
295 297
296 298 Fingerprints
297 299
298 300 - works without cacerts (hostkeyfingerprints)
299 301 $ hg -R copy-pull id https://localhost:$HGPORT/ --insecure --config hostfingerprints.localhost=91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca
300 302 5fed3813f7f5
301 303
302 304 - works without cacerts (hostsecurity)
303 305 $ hg -R copy-pull id https://localhost:$HGPORT/ --config hostsecurity.localhost:fingerprints=sha1:914f1aff87249c09b6859b88b1906d30756491ca
304 306 5fed3813f7f5
305 307
306 308 $ hg -R copy-pull id https://localhost:$HGPORT/ --config hostsecurity.localhost:fingerprints=sha256:62:09:97:2f:97:60:e3:65:8f:12:5d:78:9e:35:a1:36:7a:65:4b:0e:9f:ac:db:c3:bc:6e:b6:a3:c0:16:e0:30
307 309 5fed3813f7f5
308 310
309 311 - multiple fingerprints specified and first matches
310 312 $ hg --config 'hostfingerprints.localhost=914f1aff87249c09b6859b88b1906d30756491ca, deadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/ --insecure
311 313 5fed3813f7f5
312 314
313 315 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:914f1aff87249c09b6859b88b1906d30756491ca, sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/
314 316 5fed3813f7f5
315 317
316 318 - multiple fingerprints specified and last matches
317 319 $ hg --config 'hostfingerprints.localhost=deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, 914f1aff87249c09b6859b88b1906d30756491ca' -R copy-pull id https://localhost:$HGPORT/ --insecure
318 320 5fed3813f7f5
319 321
320 322 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, sha1:914f1aff87249c09b6859b88b1906d30756491ca' -R copy-pull id https://localhost:$HGPORT/
321 323 5fed3813f7f5
322 324
323 325 - multiple fingerprints specified and none match
324 326
325 327 $ hg --config 'hostfingerprints.localhost=deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, aeadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/ --insecure
326 328 abort: certificate for localhost has unexpected fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca
327 329 (check hostfingerprint configuration)
328 330 [255]
329 331
330 332 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, sha1:aeadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/
331 333 abort: certificate for localhost has unexpected fingerprint sha1:91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca
332 334 (check hostsecurity configuration)
333 335 [255]
334 336
335 337 - fails when cert doesn't match hostname (port is ignored)
336 338 $ hg -R copy-pull id https://localhost:$HGPORT1/ --config hostfingerprints.localhost=914f1aff87249c09b6859b88b1906d30756491ca
337 339 abort: certificate for localhost has unexpected fingerprint 28:ff:71:bf:65:31:14:23:ad:62:92:b4:0e:31:99:18:fc:83:e3:9b
338 340 (check hostfingerprint configuration)
339 341 [255]
340 342
341 343
342 344 - ignores that certificate doesn't match hostname
343 345 $ hg -R copy-pull id https://127.0.0.1:$HGPORT/ --config hostfingerprints.127.0.0.1=914f1aff87249c09b6859b88b1906d30756491ca
344 346 5fed3813f7f5
345 347
346 348 HGPORT1 is reused below for tinyproxy tests. Kill that server.
347 349 $ killdaemons.py hg1.pid
348 350
349 351 Prepare for connecting through proxy
350 352
351 353 $ tinyproxy.py $HGPORT1 localhost >proxy.log </dev/null 2>&1 &
352 354 $ while [ ! -f proxy.pid ]; do sleep 0; done
353 355 $ cat proxy.pid >> $DAEMON_PIDS
354 356
355 357 $ echo "[http_proxy]" >> copy-pull/.hg/hgrc
356 358 $ echo "always=True" >> copy-pull/.hg/hgrc
357 359 $ echo "[hostfingerprints]" >> copy-pull/.hg/hgrc
358 360 $ echo "localhost =" >> copy-pull/.hg/hgrc
359 361
360 362 Test unvalidated https through proxy
361 363
362 364 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --insecure --traceback
363 365 pulling from https://localhost:$HGPORT/
364 366 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
365 367 searching for changes
366 368 no changes found
367 369
368 370 Test https with cacert and fingerprint through proxy
369 371
370 372 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull \
371 373 > --config web.cacerts="$CERTSDIR/pub.pem"
372 374 pulling from https://localhost:$HGPORT/
373 375 searching for changes
374 376 no changes found
375 377 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull https://127.0.0.1:$HGPORT/ --config hostfingerprints.127.0.0.1=914f1aff87249c09b6859b88b1906d30756491ca
376 378 pulling from https://127.0.0.1:$HGPORT/
377 379 searching for changes
378 380 no changes found
379 381
380 382 Test https with cert problems through proxy
381 383
382 384 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull \
383 385 > --config web.cacerts="$CERTSDIR/pub-other.pem"
384 386 pulling from https://localhost:$HGPORT/
385 387 abort: error: *certificate verify failed* (glob)
386 388 [255]
387 389 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull \
388 390 > --config web.cacerts="$CERTSDIR/pub-expired.pem" https://localhost:$HGPORT2/
389 391 pulling from https://localhost:$HGPORT2/
390 392 abort: error: *certificate verify failed* (glob)
391 393 [255]
392 394
393 395
394 396 $ killdaemons.py hg0.pid
395 397
396 398 #if sslcontext
397 399
398 400 Start patched hgweb that requires client certificates:
399 401
400 402 $ cat << EOT > reqclientcert.py
401 403 > import ssl
402 404 > from mercurial.hgweb import server
403 405 > class _httprequesthandlersslclientcert(server._httprequesthandlerssl):
404 406 > @staticmethod
405 407 > def preparehttpserver(httpserver, ssl_cert):
406 408 > sslcontext = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
407 409 > sslcontext.verify_mode = ssl.CERT_REQUIRED
408 410 > sslcontext.load_cert_chain(ssl_cert)
409 411 > # verify clients by server certificate
410 412 > sslcontext.load_verify_locations(ssl_cert)
411 413 > httpserver.socket = sslcontext.wrap_socket(httpserver.socket,
412 414 > server_side=True)
413 415 > server._httprequesthandlerssl = _httprequesthandlersslclientcert
414 416 > EOT
415 417 $ cd test
416 418 $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV \
417 419 > --config extensions.reqclientcert=../reqclientcert.py
418 420 $ cat ../hg0.pid >> $DAEMON_PIDS
419 421 $ cd ..
420 422
421 423 without client certificate:
422 424
423 425 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/
424 426 abort: error: *handshake failure* (glob)
425 427 [255]
426 428
427 429 with client certificate:
428 430
429 431 $ cat << EOT >> $HGRCPATH
430 432 > [auth]
431 433 > l.prefix = localhost
432 434 > l.cert = $CERTSDIR/client-cert.pem
433 435 > l.key = $CERTSDIR/client-key.pem
434 436 > EOT
435 437
436 438 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
437 439 > --config auth.l.key="$CERTSDIR/client-key-decrypted.pem"
438 440 5fed3813f7f5
439 441
440 442 $ printf '1234\n' | env P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
441 443 > --config ui.interactive=True --config ui.nontty=True
442 444 passphrase for */client-key.pem: 5fed3813f7f5 (glob)
443 445
444 446 $ env P="$CERTSDIR" hg id https://localhost:$HGPORT/
445 447 abort: error: * (glob)
446 448 [255]
447 449
448 450 #endif
@@ -1,121 +1,123
1 1 #require serve ssl
2 2
3 3 Set up SMTP server:
4 4
5 5 $ CERTSDIR="$TESTDIR/sslcerts"
6 6 $ cat "$CERTSDIR/priv.pem" "$CERTSDIR/pub.pem" >> server.pem
7 7
8 8 $ python "$TESTDIR/dummysmtpd.py" -p $HGPORT --pid-file a.pid -d \
9 9 > --tls smtps --certificate `pwd`/server.pem
10 10 listening at localhost:$HGPORT
11 11 $ cat a.pid >> $DAEMON_PIDS
12 12
13 13 Ensure hg email output is sent to stdout:
14 14
15 15 $ unset PAGER
16 16
17 17 Set up repository:
18 18
19 19 $ hg init t
20 20 $ cd t
21 21 $ cat <<EOF >> .hg/hgrc
22 22 > [extensions]
23 23 > patchbomb =
24 24 > [email]
25 25 > method = smtp
26 26 > [smtp]
27 27 > host = localhost
28 28 > port = $HGPORT
29 29 > tls = smtps
30 30 > EOF
31 31
32 32 $ echo a > a
33 33 $ hg commit -Ama -d '1 0'
34 34 adding a
35 35
36 36 Utility functions:
37 37
38 38 $ DISABLECACERTS=
39 39 $ try () {
40 40 > hg email $DISABLECACERTS -f quux -t foo -c bar -r tip "$@"
41 41 > }
42 42
43 43 Our test cert is not signed by a trusted CA. It should fail to verify if
44 44 we are able to load CA certs:
45 45
46 46 #if sslcontext defaultcacerts no-defaultcacertsloaded
47 47 $ try
48 48 this patch series consists of 1 patches.
49 49
50 50
51 51 (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)
52 52 (?i)abort: .*?certificate.verify.failed.* (re)
53 53 [255]
54 54 #endif
55 55
56 56 #if no-sslcontext defaultcacerts
57 57 $ try
58 58 this patch series consists of 1 patches.
59 59
60 60
61 (using CA certificates from *; if you see this message, your Mercurial install is not properly configured; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message) (glob) (?)
61 62 (?i)abort: .*?certificate.verify.failed.* (re)
62 63 [255]
63 64 #endif
64 65
65 66 #if defaultcacertsloaded
66 67 $ try
67 68 this patch series consists of 1 patches.
68 69
69 70
71 (using CA certificates from *; if you see this message, your Mercurial install is not properly configured; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message) (glob) (?)
70 72 (?i)abort: .*?certificate.verify.failed.* (re)
71 73 [255]
72 74
73 75 #endif
74 76
75 77 #if no-defaultcacerts
76 78 $ try
77 79 this patch series consists of 1 patches.
78 80
79 81
80 82 (unable to load * certificates; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message) (glob) (?)
81 83 abort: localhost certificate error: no certificate received
82 84 (set hostsecurity.localhost:certfingerprints=sha256:62:09:97:2f:97:60:e3:65:8f:12:5d:78:9e:35:a1:36:7a:65:4b:0e:9f:ac:db:c3:bc:6e:b6:a3:c0:16:e0:30 config setting or use --insecure to connect insecurely)
83 85 [255]
84 86 #endif
85 87
86 88 $ DISABLECACERTS="--config devel.disableloaddefaultcerts=true"
87 89
88 90 Without certificates:
89 91
90 92 $ try --debug
91 93 this patch series consists of 1 patches.
92 94
93 95
94 96 (using smtps)
95 97 sending mail: smtp host localhost, port * (glob)
96 98 (verifying remote certificate)
97 99 abort: unable to verify security of localhost (no loaded CA certificates); refusing to connect
98 100 (see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error or set hostsecurity.localhost:fingerprints=sha256:62:09:97:2f:97:60:e3:65:8f:12:5d:78:9e:35:a1:36:7a:65:4b:0e:9f:ac:db:c3:bc:6e:b6:a3:c0:16:e0:30 to trust this server)
99 101 [255]
100 102
101 103 With global certificates:
102 104
103 105 $ try --debug --config web.cacerts="$CERTSDIR/pub.pem"
104 106 this patch series consists of 1 patches.
105 107
106 108
107 109 (using smtps)
108 110 sending mail: smtp host localhost, port * (glob)
109 111 (verifying remote certificate)
110 112 sending [PATCH] a ...
111 113
112 114 With invalid certificates:
113 115
114 116 $ try --config web.cacerts="$CERTSDIR/pub-other.pem"
115 117 this patch series consists of 1 patches.
116 118
117 119
118 120 (?i)abort: .*?certificate.verify.failed.* (re)
119 121 [255]
120 122
121 123 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now