##// END OF EJS Templates
sslutil: issue warning when [hostfingerprint] is used...
Gregory Szorc -
r31290:f819aa9d default
parent child Browse files
Show More
@@ -1,848 +1,858 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 pycompat,
22 22 util,
23 23 )
24 24
25 25 # Python 2.7.9+ overhauled the built-in SSL/TLS features of Python. It added
26 26 # support for TLS 1.1, TLS 1.2, SNI, system CA stores, etc. These features are
27 27 # all exposed via the "ssl" module.
28 28 #
29 29 # Depending on the version of Python being used, SSL/TLS support is either
30 30 # modern/secure or legacy/insecure. Many operations in this module have
31 31 # separate code paths depending on support in Python.
32 32
33 33 configprotocols = set([
34 34 'tls1.0',
35 35 'tls1.1',
36 36 'tls1.2',
37 37 ])
38 38
39 39 hassni = getattr(ssl, 'HAS_SNI', False)
40 40
41 41 # TLS 1.1 and 1.2 may not be supported if the OpenSSL Python is compiled
42 42 # against doesn't support them.
43 43 supportedprotocols = set(['tls1.0'])
44 44 if util.safehasattr(ssl, 'PROTOCOL_TLSv1_1'):
45 45 supportedprotocols.add('tls1.1')
46 46 if util.safehasattr(ssl, 'PROTOCOL_TLSv1_2'):
47 47 supportedprotocols.add('tls1.2')
48 48
49 49 try:
50 50 # ssl.SSLContext was added in 2.7.9 and presence indicates modern
51 51 # SSL/TLS features are available.
52 52 SSLContext = ssl.SSLContext
53 53 modernssl = True
54 54 _canloaddefaultcerts = util.safehasattr(SSLContext, 'load_default_certs')
55 55 except AttributeError:
56 56 modernssl = False
57 57 _canloaddefaultcerts = False
58 58
59 59 # We implement SSLContext using the interface from the standard library.
60 60 class SSLContext(object):
61 61 # ssl.wrap_socket gained the "ciphers" named argument in 2.7.
62 62 _supportsciphers = sys.version_info >= (2, 7)
63 63
64 64 def __init__(self, protocol):
65 65 # From the public interface of SSLContext
66 66 self.protocol = protocol
67 67 self.check_hostname = False
68 68 self.options = 0
69 69 self.verify_mode = ssl.CERT_NONE
70 70
71 71 # Used by our implementation.
72 72 self._certfile = None
73 73 self._keyfile = None
74 74 self._certpassword = None
75 75 self._cacerts = None
76 76 self._ciphers = None
77 77
78 78 def load_cert_chain(self, certfile, keyfile=None, password=None):
79 79 self._certfile = certfile
80 80 self._keyfile = keyfile
81 81 self._certpassword = password
82 82
83 83 def load_default_certs(self, purpose=None):
84 84 pass
85 85
86 86 def load_verify_locations(self, cafile=None, capath=None, cadata=None):
87 87 if capath:
88 88 raise error.Abort(_('capath not supported'))
89 89 if cadata:
90 90 raise error.Abort(_('cadata not supported'))
91 91
92 92 self._cacerts = cafile
93 93
94 94 def set_ciphers(self, ciphers):
95 95 if not self._supportsciphers:
96 96 raise error.Abort(_('setting ciphers in [hostsecurity] is not '
97 97 'supported by this version of Python'),
98 98 hint=_('remove the config option or run '
99 99 'Mercurial with a modern Python '
100 100 'version (preferred)'))
101 101
102 102 self._ciphers = ciphers
103 103
104 104 def wrap_socket(self, socket, server_hostname=None, server_side=False):
105 105 # server_hostname is unique to SSLContext.wrap_socket and is used
106 106 # for SNI in that context. So there's nothing for us to do with it
107 107 # in this legacy code since we don't support SNI.
108 108
109 109 args = {
110 110 'keyfile': self._keyfile,
111 111 'certfile': self._certfile,
112 112 'server_side': server_side,
113 113 'cert_reqs': self.verify_mode,
114 114 'ssl_version': self.protocol,
115 115 'ca_certs': self._cacerts,
116 116 }
117 117
118 118 if self._supportsciphers:
119 119 args['ciphers'] = self._ciphers
120 120
121 121 return ssl.wrap_socket(socket, **args)
122 122
123 123 def _hostsettings(ui, hostname):
124 124 """Obtain security settings for a hostname.
125 125
126 126 Returns a dict of settings relevant to that hostname.
127 127 """
128 128 s = {
129 129 # Whether we should attempt to load default/available CA certs
130 130 # if an explicit ``cafile`` is not defined.
131 131 'allowloaddefaultcerts': True,
132 132 # List of 2-tuple of (hash algorithm, hash).
133 133 'certfingerprints': [],
134 134 # Path to file containing concatenated CA certs. Used by
135 135 # SSLContext.load_verify_locations().
136 136 'cafile': None,
137 137 # Whether certificate verification should be disabled.
138 138 'disablecertverification': False,
139 139 # Whether the legacy [hostfingerprints] section has data for this host.
140 140 'legacyfingerprint': False,
141 141 # PROTOCOL_* constant to use for SSLContext.__init__.
142 142 'protocol': None,
143 143 # String representation of minimum protocol to be used for UI
144 144 # presentation.
145 145 'protocolui': None,
146 146 # ssl.CERT_* constant used by SSLContext.verify_mode.
147 147 'verifymode': None,
148 148 # Defines extra ssl.OP* bitwise options to set.
149 149 'ctxoptions': None,
150 150 # OpenSSL Cipher List to use (instead of default).
151 151 'ciphers': None,
152 152 }
153 153
154 154 # Allow minimum TLS protocol to be specified in the config.
155 155 def validateprotocol(protocol, key):
156 156 if protocol not in configprotocols:
157 157 raise error.Abort(
158 158 _('unsupported protocol from hostsecurity.%s: %s') %
159 159 (key, protocol),
160 160 hint=_('valid protocols: %s') %
161 161 ' '.join(sorted(configprotocols)))
162 162
163 163 # We default to TLS 1.1+ where we can because TLS 1.0 has known
164 164 # vulnerabilities (like BEAST and POODLE). We allow users to downgrade to
165 165 # TLS 1.0+ via config options in case a legacy server is encountered.
166 166 if 'tls1.1' in supportedprotocols:
167 167 defaultprotocol = 'tls1.1'
168 168 else:
169 169 # Let people know they are borderline secure.
170 170 # We don't document this config option because we want people to see
171 171 # the bold warnings on the web site.
172 172 # internal config: hostsecurity.disabletls10warning
173 173 if not ui.configbool('hostsecurity', 'disabletls10warning'):
174 174 ui.warn(_('warning: connecting to %s using legacy security '
175 175 'technology (TLS 1.0); see '
176 176 'https://mercurial-scm.org/wiki/SecureConnections for '
177 177 'more info\n') % hostname)
178 178 defaultprotocol = 'tls1.0'
179 179
180 180 key = 'minimumprotocol'
181 181 protocol = ui.config('hostsecurity', key, defaultprotocol)
182 182 validateprotocol(protocol, key)
183 183
184 184 key = '%s:minimumprotocol' % hostname
185 185 protocol = ui.config('hostsecurity', key, protocol)
186 186 validateprotocol(protocol, key)
187 187
188 188 # If --insecure is used, we allow the use of TLS 1.0 despite config options.
189 189 # We always print a "connection security to %s is disabled..." message when
190 190 # --insecure is used. So no need to print anything more here.
191 191 if ui.insecureconnections:
192 192 protocol = 'tls1.0'
193 193
194 194 s['protocol'], s['ctxoptions'], s['protocolui'] = protocolsettings(protocol)
195 195
196 196 ciphers = ui.config('hostsecurity', 'ciphers')
197 197 ciphers = ui.config('hostsecurity', '%s:ciphers' % hostname, ciphers)
198 198 s['ciphers'] = ciphers
199 199
200 200 # Look for fingerprints in [hostsecurity] section. Value is a list
201 201 # of <alg>:<fingerprint> strings.
202 202 fingerprints = ui.configlist('hostsecurity', '%s:fingerprints' % hostname,
203 203 [])
204 204 for fingerprint in fingerprints:
205 205 if not (fingerprint.startswith(('sha1:', 'sha256:', 'sha512:'))):
206 206 raise error.Abort(_('invalid fingerprint for %s: %s') % (
207 207 hostname, fingerprint),
208 208 hint=_('must begin with "sha1:", "sha256:", '
209 209 'or "sha512:"'))
210 210
211 211 alg, fingerprint = fingerprint.split(':', 1)
212 212 fingerprint = fingerprint.replace(':', '').lower()
213 213 s['certfingerprints'].append((alg, fingerprint))
214 214
215 215 # Fingerprints from [hostfingerprints] are always SHA-1.
216 216 for fingerprint in ui.configlist('hostfingerprints', hostname, []):
217 217 fingerprint = fingerprint.replace(':', '').lower()
218 218 s['certfingerprints'].append(('sha1', fingerprint))
219 219 s['legacyfingerprint'] = True
220 220
221 221 # If a host cert fingerprint is defined, it is the only thing that
222 222 # matters. No need to validate CA certs.
223 223 if s['certfingerprints']:
224 224 s['verifymode'] = ssl.CERT_NONE
225 225 s['allowloaddefaultcerts'] = False
226 226
227 227 # If --insecure is used, don't take CAs into consideration.
228 228 elif ui.insecureconnections:
229 229 s['disablecertverification'] = True
230 230 s['verifymode'] = ssl.CERT_NONE
231 231 s['allowloaddefaultcerts'] = False
232 232
233 233 if ui.configbool('devel', 'disableloaddefaultcerts'):
234 234 s['allowloaddefaultcerts'] = False
235 235
236 236 # If both fingerprints and a per-host ca file are specified, issue a warning
237 237 # because users should not be surprised about what security is or isn't
238 238 # being performed.
239 239 cafile = ui.config('hostsecurity', '%s:verifycertsfile' % hostname)
240 240 if s['certfingerprints'] and cafile:
241 241 ui.warn(_('(hostsecurity.%s:verifycertsfile ignored when host '
242 242 'fingerprints defined; using host fingerprints for '
243 243 'verification)\n') % hostname)
244 244
245 245 # Try to hook up CA certificate validation unless something above
246 246 # makes it not necessary.
247 247 if s['verifymode'] is None:
248 248 # Look at per-host ca file first.
249 249 if cafile:
250 250 cafile = util.expandpath(cafile)
251 251 if not os.path.exists(cafile):
252 252 raise error.Abort(_('path specified by %s does not exist: %s') %
253 253 ('hostsecurity.%s:verifycertsfile' % hostname,
254 254 cafile))
255 255 s['cafile'] = cafile
256 256 else:
257 257 # Find global certificates file in config.
258 258 cafile = ui.config('web', 'cacerts')
259 259
260 260 if cafile:
261 261 cafile = util.expandpath(cafile)
262 262 if not os.path.exists(cafile):
263 263 raise error.Abort(_('could not find web.cacerts: %s') %
264 264 cafile)
265 265 elif s['allowloaddefaultcerts']:
266 266 # CAs not defined in config. Try to find system bundles.
267 267 cafile = _defaultcacerts(ui)
268 268 if cafile:
269 269 ui.debug('using %s for CA file\n' % cafile)
270 270
271 271 s['cafile'] = cafile
272 272
273 273 # Require certificate validation if CA certs are being loaded and
274 274 # verification hasn't been disabled above.
275 275 if cafile or (_canloaddefaultcerts and s['allowloaddefaultcerts']):
276 276 s['verifymode'] = ssl.CERT_REQUIRED
277 277 else:
278 278 # At this point we don't have a fingerprint, aren't being
279 279 # explicitly insecure, and can't load CA certs. Connecting
280 280 # is insecure. We allow the connection and abort during
281 281 # validation (once we have the fingerprint to print to the
282 282 # user).
283 283 s['verifymode'] = ssl.CERT_NONE
284 284
285 285 assert s['protocol'] is not None
286 286 assert s['ctxoptions'] is not None
287 287 assert s['verifymode'] is not None
288 288
289 289 return s
290 290
291 291 def protocolsettings(protocol):
292 292 """Resolve the protocol for a config value.
293 293
294 294 Returns a 3-tuple of (protocol, options, ui value) where the first
295 295 2 items are values used by SSLContext and the last is a string value
296 296 of the ``minimumprotocol`` config option equivalent.
297 297 """
298 298 if protocol not in configprotocols:
299 299 raise ValueError('protocol value not supported: %s' % protocol)
300 300
301 301 # Despite its name, PROTOCOL_SSLv23 selects the highest protocol
302 302 # that both ends support, including TLS protocols. On legacy stacks,
303 303 # the highest it likely goes is TLS 1.0. On modern stacks, it can
304 304 # support TLS 1.2.
305 305 #
306 306 # The PROTOCOL_TLSv* constants select a specific TLS version
307 307 # only (as opposed to multiple versions). So the method for
308 308 # supporting multiple TLS versions is to use PROTOCOL_SSLv23 and
309 309 # disable protocols via SSLContext.options and OP_NO_* constants.
310 310 # However, SSLContext.options doesn't work unless we have the
311 311 # full/real SSLContext available to us.
312 312 if supportedprotocols == set(['tls1.0']):
313 313 if protocol != 'tls1.0':
314 314 raise error.Abort(_('current Python does not support protocol '
315 315 'setting %s') % protocol,
316 316 hint=_('upgrade Python or disable setting since '
317 317 'only TLS 1.0 is supported'))
318 318
319 319 return ssl.PROTOCOL_TLSv1, 0, 'tls1.0'
320 320
321 321 # WARNING: returned options don't work unless the modern ssl module
322 322 # is available. Be careful when adding options here.
323 323
324 324 # SSLv2 and SSLv3 are broken. We ban them outright.
325 325 options = ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3
326 326
327 327 if protocol == 'tls1.0':
328 328 # Defaults above are to use TLS 1.0+
329 329 pass
330 330 elif protocol == 'tls1.1':
331 331 options |= ssl.OP_NO_TLSv1
332 332 elif protocol == 'tls1.2':
333 333 options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
334 334 else:
335 335 raise error.Abort(_('this should not happen'))
336 336
337 337 # Prevent CRIME.
338 338 # There is no guarantee this attribute is defined on the module.
339 339 options |= getattr(ssl, 'OP_NO_COMPRESSION', 0)
340 340
341 341 return ssl.PROTOCOL_SSLv23, options, protocol
342 342
343 343 def wrapsocket(sock, keyfile, certfile, ui, serverhostname=None):
344 344 """Add SSL/TLS to a socket.
345 345
346 346 This is a glorified wrapper for ``ssl.wrap_socket()``. It makes sane
347 347 choices based on what security options are available.
348 348
349 349 In addition to the arguments supported by ``ssl.wrap_socket``, we allow
350 350 the following additional arguments:
351 351
352 352 * serverhostname - The expected hostname of the remote server. If the
353 353 server (and client) support SNI, this tells the server which certificate
354 354 to use.
355 355 """
356 356 if not serverhostname:
357 357 raise error.Abort(_('serverhostname argument is required'))
358 358
359 359 settings = _hostsettings(ui, serverhostname)
360 360
361 361 # We can't use ssl.create_default_context() because it calls
362 362 # load_default_certs() unless CA arguments are passed to it. We want to
363 363 # have explicit control over CA loading because implicitly loading
364 364 # CAs may undermine the user's intent. For example, a user may define a CA
365 365 # bundle with a specific CA cert removed. If the system/default CA bundle
366 366 # is loaded and contains that removed CA, you've just undone the user's
367 367 # choice.
368 368 sslcontext = SSLContext(settings['protocol'])
369 369
370 370 # This is a no-op unless using modern ssl.
371 371 sslcontext.options |= settings['ctxoptions']
372 372
373 373 # This still works on our fake SSLContext.
374 374 sslcontext.verify_mode = settings['verifymode']
375 375
376 376 if settings['ciphers']:
377 377 try:
378 378 sslcontext.set_ciphers(settings['ciphers'])
379 379 except ssl.SSLError as e:
380 380 raise error.Abort(_('could not set ciphers: %s') % e.args[0],
381 381 hint=_('change cipher string (%s) in config') %
382 382 settings['ciphers'])
383 383
384 384 if certfile is not None:
385 385 def password():
386 386 f = keyfile or certfile
387 387 return ui.getpass(_('passphrase for %s: ') % f, '')
388 388 sslcontext.load_cert_chain(certfile, keyfile, password)
389 389
390 390 if settings['cafile'] is not None:
391 391 try:
392 392 sslcontext.load_verify_locations(cafile=settings['cafile'])
393 393 except ssl.SSLError as e:
394 394 if len(e.args) == 1: # pypy has different SSLError args
395 395 msg = e.args[0]
396 396 else:
397 397 msg = e.args[1]
398 398 raise error.Abort(_('error loading CA file %s: %s') % (
399 399 settings['cafile'], msg),
400 400 hint=_('file is empty or malformed?'))
401 401 caloaded = True
402 402 elif settings['allowloaddefaultcerts']:
403 403 # This is a no-op on old Python.
404 404 sslcontext.load_default_certs()
405 405 caloaded = True
406 406 else:
407 407 caloaded = False
408 408
409 409 try:
410 410 sslsocket = sslcontext.wrap_socket(sock, server_hostname=serverhostname)
411 411 except ssl.SSLError as e:
412 412 # If we're doing certificate verification and no CA certs are loaded,
413 413 # that is almost certainly the reason why verification failed. Provide
414 414 # a hint to the user.
415 415 # Only modern ssl module exposes SSLContext.get_ca_certs() so we can
416 416 # only show this warning if modern ssl is available.
417 417 # The exception handler is here because of
418 418 # https://bugs.python.org/issue20916.
419 419 try:
420 420 if (caloaded and settings['verifymode'] == ssl.CERT_REQUIRED and
421 421 modernssl and not sslcontext.get_ca_certs()):
422 422 ui.warn(_('(an attempt was made to load CA certificates but '
423 423 'none were loaded; see '
424 424 'https://mercurial-scm.org/wiki/SecureConnections '
425 425 'for how to configure Mercurial to avoid this '
426 426 'error)\n'))
427 427 except ssl.SSLError:
428 428 pass
429 429 # Try to print more helpful error messages for known failures.
430 430 if util.safehasattr(e, 'reason'):
431 431 # This error occurs when the client and server don't share a
432 432 # common/supported SSL/TLS protocol. We've disabled SSLv2 and SSLv3
433 433 # outright. Hopefully the reason for this error is that we require
434 434 # TLS 1.1+ and the server only supports TLS 1.0. Whatever the
435 435 # reason, try to emit an actionable warning.
436 436 if e.reason == 'UNSUPPORTED_PROTOCOL':
437 437 # We attempted TLS 1.0+.
438 438 if settings['protocolui'] == 'tls1.0':
439 439 # We support more than just TLS 1.0+. If this happens,
440 440 # the likely scenario is either the client or the server
441 441 # is really old. (e.g. server doesn't support TLS 1.0+ or
442 442 # client doesn't support modern TLS versions introduced
443 443 # several years from when this comment was written).
444 444 if supportedprotocols != set(['tls1.0']):
445 445 ui.warn(_(
446 446 '(could not communicate with %s using security '
447 447 'protocols %s; if you are using a modern Mercurial '
448 448 'version, consider contacting the operator of this '
449 449 'server; see '
450 450 'https://mercurial-scm.org/wiki/SecureConnections '
451 451 'for more info)\n') % (
452 452 serverhostname,
453 453 ', '.join(sorted(supportedprotocols))))
454 454 else:
455 455 ui.warn(_(
456 456 '(could not communicate with %s using TLS 1.0; the '
457 457 'likely cause of this is the server no longer '
458 458 'supports TLS 1.0 because it has known security '
459 459 'vulnerabilities; see '
460 460 'https://mercurial-scm.org/wiki/SecureConnections '
461 461 'for more info)\n') % serverhostname)
462 462 else:
463 463 # We attempted TLS 1.1+. We can only get here if the client
464 464 # supports the configured protocol. So the likely reason is
465 465 # the client wants better security than the server can
466 466 # offer.
467 467 ui.warn(_(
468 468 '(could not negotiate a common security protocol (%s+) '
469 469 'with %s; the likely cause is Mercurial is configured '
470 470 'to be more secure than the server can support)\n') % (
471 471 settings['protocolui'], serverhostname))
472 472 ui.warn(_('(consider contacting the operator of this '
473 473 'server and ask them to support modern TLS '
474 474 'protocol versions; or, set '
475 475 'hostsecurity.%s:minimumprotocol=tls1.0 to allow '
476 476 'use of legacy, less secure protocols when '
477 477 'communicating with this server)\n') %
478 478 serverhostname)
479 479 ui.warn(_(
480 480 '(see https://mercurial-scm.org/wiki/SecureConnections '
481 481 'for more info)\n'))
482 482 raise
483 483
484 484 # check if wrap_socket failed silently because socket had been
485 485 # closed
486 486 # - see http://bugs.python.org/issue13721
487 487 if not sslsocket.cipher():
488 488 raise error.Abort(_('ssl connection failed'))
489 489
490 490 sslsocket._hgstate = {
491 491 'caloaded': caloaded,
492 492 'hostname': serverhostname,
493 493 'settings': settings,
494 494 'ui': ui,
495 495 }
496 496
497 497 return sslsocket
498 498
499 499 def wrapserversocket(sock, ui, certfile=None, keyfile=None, cafile=None,
500 500 requireclientcert=False):
501 501 """Wrap a socket for use by servers.
502 502
503 503 ``certfile`` and ``keyfile`` specify the files containing the certificate's
504 504 public and private keys, respectively. Both keys can be defined in the same
505 505 file via ``certfile`` (the private key must come first in the file).
506 506
507 507 ``cafile`` defines the path to certificate authorities.
508 508
509 509 ``requireclientcert`` specifies whether to require client certificates.
510 510
511 511 Typically ``cafile`` is only defined if ``requireclientcert`` is true.
512 512 """
513 513 protocol, options, _protocolui = protocolsettings('tls1.0')
514 514
515 515 # This config option is intended for use in tests only. It is a giant
516 516 # footgun to kill security. Don't define it.
517 517 exactprotocol = ui.config('devel', 'serverexactprotocol')
518 518 if exactprotocol == 'tls1.0':
519 519 protocol = ssl.PROTOCOL_TLSv1
520 520 elif exactprotocol == 'tls1.1':
521 521 if 'tls1.1' not in supportedprotocols:
522 522 raise error.Abort(_('TLS 1.1 not supported by this Python'))
523 523 protocol = ssl.PROTOCOL_TLSv1_1
524 524 elif exactprotocol == 'tls1.2':
525 525 if 'tls1.2' not in supportedprotocols:
526 526 raise error.Abort(_('TLS 1.2 not supported by this Python'))
527 527 protocol = ssl.PROTOCOL_TLSv1_2
528 528 elif exactprotocol:
529 529 raise error.Abort(_('invalid value for serverexactprotocol: %s') %
530 530 exactprotocol)
531 531
532 532 if modernssl:
533 533 # We /could/ use create_default_context() here since it doesn't load
534 534 # CAs when configured for client auth. However, it is hard-coded to
535 535 # use ssl.PROTOCOL_SSLv23 which may not be appropriate here.
536 536 sslcontext = SSLContext(protocol)
537 537 sslcontext.options |= options
538 538
539 539 # Improve forward secrecy.
540 540 sslcontext.options |= getattr(ssl, 'OP_SINGLE_DH_USE', 0)
541 541 sslcontext.options |= getattr(ssl, 'OP_SINGLE_ECDH_USE', 0)
542 542
543 543 # Use the list of more secure ciphers if found in the ssl module.
544 544 if util.safehasattr(ssl, '_RESTRICTED_SERVER_CIPHERS'):
545 545 sslcontext.options |= getattr(ssl, 'OP_CIPHER_SERVER_PREFERENCE', 0)
546 546 sslcontext.set_ciphers(ssl._RESTRICTED_SERVER_CIPHERS)
547 547 else:
548 548 sslcontext = SSLContext(ssl.PROTOCOL_TLSv1)
549 549
550 550 if requireclientcert:
551 551 sslcontext.verify_mode = ssl.CERT_REQUIRED
552 552 else:
553 553 sslcontext.verify_mode = ssl.CERT_NONE
554 554
555 555 if certfile or keyfile:
556 556 sslcontext.load_cert_chain(certfile=certfile, keyfile=keyfile)
557 557
558 558 if cafile:
559 559 sslcontext.load_verify_locations(cafile=cafile)
560 560
561 561 return sslcontext.wrap_socket(sock, server_side=True)
562 562
563 563 class wildcarderror(Exception):
564 564 """Represents an error parsing wildcards in DNS name."""
565 565
566 566 def _dnsnamematch(dn, hostname, maxwildcards=1):
567 567 """Match DNS names according RFC 6125 section 6.4.3.
568 568
569 569 This code is effectively copied from CPython's ssl._dnsname_match.
570 570
571 571 Returns a bool indicating whether the expected hostname matches
572 572 the value in ``dn``.
573 573 """
574 574 pats = []
575 575 if not dn:
576 576 return False
577 577
578 578 pieces = dn.split(r'.')
579 579 leftmost = pieces[0]
580 580 remainder = pieces[1:]
581 581 wildcards = leftmost.count('*')
582 582 if wildcards > maxwildcards:
583 583 raise wildcarderror(
584 584 _('too many wildcards in certificate DNS name: %s') % dn)
585 585
586 586 # speed up common case w/o wildcards
587 587 if not wildcards:
588 588 return dn.lower() == hostname.lower()
589 589
590 590 # RFC 6125, section 6.4.3, subitem 1.
591 591 # The client SHOULD NOT attempt to match a presented identifier in which
592 592 # the wildcard character comprises a label other than the left-most label.
593 593 if leftmost == '*':
594 594 # When '*' is a fragment by itself, it matches a non-empty dotless
595 595 # fragment.
596 596 pats.append('[^.]+')
597 597 elif leftmost.startswith('xn--') or hostname.startswith('xn--'):
598 598 # RFC 6125, section 6.4.3, subitem 3.
599 599 # The client SHOULD NOT attempt to match a presented identifier
600 600 # where the wildcard character is embedded within an A-label or
601 601 # U-label of an internationalized domain name.
602 602 pats.append(re.escape(leftmost))
603 603 else:
604 604 # Otherwise, '*' matches any dotless string, e.g. www*
605 605 pats.append(re.escape(leftmost).replace(r'\*', '[^.]*'))
606 606
607 607 # add the remaining fragments, ignore any wildcards
608 608 for frag in remainder:
609 609 pats.append(re.escape(frag))
610 610
611 611 pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)
612 612 return pat.match(hostname) is not None
613 613
614 614 def _verifycert(cert, hostname):
615 615 '''Verify that cert (in socket.getpeercert() format) matches hostname.
616 616 CRLs is not handled.
617 617
618 618 Returns error message if any problems are found and None on success.
619 619 '''
620 620 if not cert:
621 621 return _('no certificate received')
622 622
623 623 dnsnames = []
624 624 san = cert.get('subjectAltName', [])
625 625 for key, value in san:
626 626 if key == 'DNS':
627 627 try:
628 628 if _dnsnamematch(value, hostname):
629 629 return
630 630 except wildcarderror as e:
631 631 return e.args[0]
632 632
633 633 dnsnames.append(value)
634 634
635 635 if not dnsnames:
636 636 # The subject is only checked when there is no DNS in subjectAltName.
637 637 for sub in cert.get('subject', []):
638 638 for key, value in sub:
639 639 # According to RFC 2818 the most specific Common Name must
640 640 # be used.
641 641 if key == 'commonName':
642 642 # 'subject' entries are unicode.
643 643 try:
644 644 value = value.encode('ascii')
645 645 except UnicodeEncodeError:
646 646 return _('IDN in certificate not supported')
647 647
648 648 try:
649 649 if _dnsnamematch(value, hostname):
650 650 return
651 651 except wildcarderror as e:
652 652 return e.args[0]
653 653
654 654 dnsnames.append(value)
655 655
656 656 if len(dnsnames) > 1:
657 657 return _('certificate is for %s') % ', '.join(dnsnames)
658 658 elif len(dnsnames) == 1:
659 659 return _('certificate is for %s') % dnsnames[0]
660 660 else:
661 661 return _('no commonName or subjectAltName found in certificate')
662 662
663 663 def _plainapplepython():
664 664 """return true if this seems to be a pure Apple Python that
665 665 * is unfrozen and presumably has the whole mercurial module in the file
666 666 system
667 667 * presumably is an Apple Python that uses Apple OpenSSL which has patches
668 668 for using system certificate store CAs in addition to the provided
669 669 cacerts file
670 670 """
671 671 if (pycompat.sysplatform != 'darwin' or
672 672 util.mainfrozen() or not pycompat.sysexecutable):
673 673 return False
674 674 exe = os.path.realpath(pycompat.sysexecutable).lower()
675 675 return (exe.startswith('/usr/bin/python') or
676 676 exe.startswith('/system/library/frameworks/python.framework/'))
677 677
678 678 _systemcacertpaths = [
679 679 # RHEL, CentOS, and Fedora
680 680 '/etc/pki/tls/certs/ca-bundle.trust.crt',
681 681 # Debian, Ubuntu, Gentoo
682 682 '/etc/ssl/certs/ca-certificates.crt',
683 683 ]
684 684
685 685 def _defaultcacerts(ui):
686 686 """return path to default CA certificates or None.
687 687
688 688 It is assumed this function is called when the returned certificates
689 689 file will actually be used to validate connections. Therefore this
690 690 function may print warnings or debug messages assuming this usage.
691 691
692 692 We don't print a message when the Python is able to load default
693 693 CA certs because this scenario is detected at socket connect time.
694 694 """
695 695 # The "certifi" Python package provides certificates. If it is installed
696 696 # and usable, assume the user intends it to be used and use it.
697 697 try:
698 698 import certifi
699 699 certs = certifi.where()
700 700 if os.path.exists(certs):
701 701 ui.debug('using ca certificates from certifi\n')
702 702 return certs
703 703 except (ImportError, AttributeError):
704 704 pass
705 705
706 706 # On Windows, only the modern ssl module is capable of loading the system
707 707 # CA certificates. If we're not capable of doing that, emit a warning
708 708 # because we'll get a certificate verification error later and the lack
709 709 # of loaded CA certificates will be the reason why.
710 710 # Assertion: this code is only called if certificates are being verified.
711 711 if pycompat.osname == 'nt':
712 712 if not _canloaddefaultcerts:
713 713 ui.warn(_('(unable to load Windows CA certificates; see '
714 714 'https://mercurial-scm.org/wiki/SecureConnections for '
715 715 'how to configure Mercurial to avoid this message)\n'))
716 716
717 717 return None
718 718
719 719 # Apple's OpenSSL has patches that allow a specially constructed certificate
720 720 # to load the system CA store. If we're running on Apple Python, use this
721 721 # trick.
722 722 if _plainapplepython():
723 723 dummycert = os.path.join(
724 724 os.path.dirname(pycompat.fsencode(__file__)), 'dummycert.pem')
725 725 if os.path.exists(dummycert):
726 726 return dummycert
727 727
728 728 # The Apple OpenSSL trick isn't available to us. If Python isn't able to
729 729 # load system certs, we're out of luck.
730 730 if pycompat.sysplatform == 'darwin':
731 731 # FUTURE Consider looking for Homebrew or MacPorts installed certs
732 732 # files. Also consider exporting the keychain certs to a file during
733 733 # Mercurial install.
734 734 if not _canloaddefaultcerts:
735 735 ui.warn(_('(unable to load CA certificates; see '
736 736 'https://mercurial-scm.org/wiki/SecureConnections for '
737 737 'how to configure Mercurial to avoid this message)\n'))
738 738 return None
739 739
740 740 # / is writable on Windows. Out of an abundance of caution make sure
741 741 # we're not on Windows because paths from _systemcacerts could be installed
742 742 # by non-admin users.
743 743 assert pycompat.osname != 'nt'
744 744
745 745 # Try to find CA certificates in well-known locations. We print a warning
746 746 # when using a found file because we don't want too much silent magic
747 747 # for security settings. The expectation is that proper Mercurial
748 748 # installs will have the CA certs path defined at install time and the
749 749 # installer/packager will make an appropriate decision on the user's
750 750 # behalf. We only get here and perform this setting as a feature of
751 751 # last resort.
752 752 if not _canloaddefaultcerts:
753 753 for path in _systemcacertpaths:
754 754 if os.path.isfile(path):
755 755 ui.warn(_('(using CA certificates from %s; if you see this '
756 756 'message, your Mercurial install is not properly '
757 757 'configured; see '
758 758 'https://mercurial-scm.org/wiki/SecureConnections '
759 759 'for how to configure Mercurial to avoid this '
760 760 'message)\n') % path)
761 761 return path
762 762
763 763 ui.warn(_('(unable to load CA certificates; see '
764 764 'https://mercurial-scm.org/wiki/SecureConnections for '
765 765 'how to configure Mercurial to avoid this message)\n'))
766 766
767 767 return None
768 768
769 769 def validatesocket(sock):
770 770 """Validate a socket meets security requirements.
771 771
772 772 The passed socket must have been created with ``wrapsocket()``.
773 773 """
774 774 host = sock._hgstate['hostname']
775 775 ui = sock._hgstate['ui']
776 776 settings = sock._hgstate['settings']
777 777
778 778 try:
779 779 peercert = sock.getpeercert(True)
780 780 peercert2 = sock.getpeercert()
781 781 except AttributeError:
782 782 raise error.Abort(_('%s ssl connection error') % host)
783 783
784 784 if not peercert:
785 785 raise error.Abort(_('%s certificate error: '
786 786 'no certificate received') % host)
787 787
788 788 if settings['disablecertverification']:
789 789 # We don't print the certificate fingerprint because it shouldn't
790 790 # be necessary: if the user requested certificate verification be
791 791 # disabled, they presumably already saw a message about the inability
792 792 # to verify the certificate and this message would have printed the
793 793 # fingerprint. So printing the fingerprint here adds little to no
794 794 # value.
795 795 ui.warn(_('warning: connection security to %s is disabled per current '
796 796 'settings; communication is susceptible to eavesdropping '
797 797 'and tampering\n') % host)
798 798 return
799 799
800 800 # If a certificate fingerprint is pinned, use it and only it to
801 801 # validate the remote cert.
802 802 peerfingerprints = {
803 803 'sha1': hashlib.sha1(peercert).hexdigest(),
804 804 'sha256': hashlib.sha256(peercert).hexdigest(),
805 805 'sha512': hashlib.sha512(peercert).hexdigest(),
806 806 }
807 807
808 808 def fmtfingerprint(s):
809 809 return ':'.join([s[x:x + 2] for x in range(0, len(s), 2)])
810 810
811 811 nicefingerprint = 'sha256:%s' % fmtfingerprint(peerfingerprints['sha256'])
812 812
813 813 if settings['certfingerprints']:
814 814 for hash, fingerprint in settings['certfingerprints']:
815 815 if peerfingerprints[hash].lower() == fingerprint:
816 816 ui.debug('%s certificate matched fingerprint %s:%s\n' %
817 817 (host, hash, fmtfingerprint(fingerprint)))
818 if settings['legacyfingerprint']:
819 ui.warn(_('(SHA-1 fingerprint for %s found in legacy '
820 '[hostfingerprints] section; '
821 'if you trust this fingerprint, set the '
822 'following config value in [hostsecurity] and '
823 'remove the old one from [hostfingerprints] '
824 'to upgrade to a more secure SHA-256 '
825 'fingerprint: '
826 '%s.fingerprints=%s)\n') % (
827 host, host, nicefingerprint))
818 828 return
819 829
820 830 # Pinned fingerprint didn't match. This is a fatal error.
821 831 if settings['legacyfingerprint']:
822 832 section = 'hostfingerprint'
823 833 nice = fmtfingerprint(peerfingerprints['sha1'])
824 834 else:
825 835 section = 'hostsecurity'
826 836 nice = '%s:%s' % (hash, fmtfingerprint(peerfingerprints[hash]))
827 837 raise error.Abort(_('certificate for %s has unexpected '
828 838 'fingerprint %s') % (host, nice),
829 839 hint=_('check %s configuration') % section)
830 840
831 841 # Security is enabled but no CAs are loaded. We can't establish trust
832 842 # for the cert so abort.
833 843 if not sock._hgstate['caloaded']:
834 844 raise error.Abort(
835 845 _('unable to verify security of %s (no loaded CA certificates); '
836 846 'refusing to connect') % host,
837 847 hint=_('see https://mercurial-scm.org/wiki/SecureConnections for '
838 848 'how to configure Mercurial to avoid this error or set '
839 849 'hostsecurity.%s:fingerprints=%s to trust this server') %
840 850 (host, nicefingerprint))
841 851
842 852 msg = _verifycert(peercert2, host)
843 853 if msg:
844 854 raise error.Abort(_('%s certificate error: %s') % (host, msg),
845 855 hint=_('set hostsecurity.%s:certfingerprints=%s '
846 856 'config setting or use --insecure to connect '
847 857 'insecurely') %
848 858 (host, nicefingerprint))
@@ -1,639 +1,644 b''
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 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
32 32 abort: could not find web.cacerts: no-such.pem
33 33 [255]
34 34
35 35 Test server address cannot be reused
36 36
37 37 #if windows
38 38 $ hg serve -p $HGPORT --certificate=$PRIV 2>&1
39 39 abort: cannot start server at 'localhost:$HGPORT':
40 40 [255]
41 41 #else
42 42 $ hg serve -p $HGPORT --certificate=$PRIV 2>&1
43 43 abort: cannot start server at 'localhost:$HGPORT': Address already in use
44 44 [255]
45 45 #endif
46 46 $ cd ..
47 47
48 48 Our test cert is not signed by a trusted CA. It should fail to verify if
49 49 we are able to load CA certs.
50 50
51 51 #if sslcontext defaultcacerts no-defaultcacertsloaded
52 52 $ hg clone https://localhost:$HGPORT/ copy-pull
53 53 (an attempt was made to load CA certificates but none were loaded; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error)
54 54 abort: error: *certificate verify failed* (glob)
55 55 [255]
56 56 #endif
57 57
58 58 #if no-sslcontext defaultcacerts
59 59 $ hg clone https://localhost:$HGPORT/ copy-pull
60 60 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
61 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) (?)
62 62 abort: error: *certificate verify failed* (glob)
63 63 [255]
64 64 #endif
65 65
66 66 #if no-sslcontext windows
67 67 $ hg clone https://localhost:$HGPORT/ copy-pull
68 68 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info
69 69 (unable to load Windows CA certificates; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message)
70 70 abort: error: *certificate verify failed* (glob)
71 71 [255]
72 72 #endif
73 73
74 74 #if no-sslcontext osx
75 75 $ hg clone https://localhost:$HGPORT/ copy-pull
76 76 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info
77 77 (unable to load CA certificates; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message)
78 78 abort: localhost certificate error: no certificate received
79 79 (set hostsecurity.localhost:certfingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e config setting or use --insecure to connect insecurely)
80 80 [255]
81 81 #endif
82 82
83 83 #if defaultcacertsloaded
84 84 $ hg clone https://localhost:$HGPORT/ copy-pull
85 85 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
86 86 (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) (?)
87 87 abort: error: *certificate verify failed* (glob)
88 88 [255]
89 89 #endif
90 90
91 91 #if no-defaultcacerts
92 92 $ hg clone https://localhost:$HGPORT/ copy-pull
93 93 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
94 94 (unable to load * certificates; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message) (glob) (?)
95 95 abort: localhost certificate error: no certificate received
96 96 (set hostsecurity.localhost:certfingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e config setting or use --insecure to connect insecurely)
97 97 [255]
98 98 #endif
99 99
100 100 Specifying a per-host certificate file that doesn't exist will abort
101 101
102 102 $ hg --config hostsecurity.localhost:verifycertsfile=/does/not/exist clone https://localhost:$HGPORT/
103 103 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
104 104 abort: path specified by hostsecurity.localhost:verifycertsfile does not exist: /does/not/exist
105 105 [255]
106 106
107 107 A malformed per-host certificate file will raise an error
108 108
109 109 $ echo baddata > badca.pem
110 110 #if sslcontext
111 111 $ hg --config hostsecurity.localhost:verifycertsfile=badca.pem clone https://localhost:$HGPORT/
112 112 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
113 113 abort: error loading CA file badca.pem: * (glob)
114 114 (file is empty or malformed?)
115 115 [255]
116 116 #else
117 117 $ hg --config hostsecurity.localhost:verifycertsfile=badca.pem clone https://localhost:$HGPORT/
118 118 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
119 119 abort: error: * (glob)
120 120 [255]
121 121 #endif
122 122
123 123 A per-host certificate mismatching the server will fail verification
124 124
125 125 (modern ssl is able to discern whether the loaded cert is a CA cert)
126 126 #if sslcontext
127 127 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/client-cert.pem" clone https://localhost:$HGPORT/
128 128 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
129 129 (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)
130 130 abort: error: *certificate verify failed* (glob)
131 131 [255]
132 132 #else
133 133 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/client-cert.pem" clone https://localhost:$HGPORT/
134 134 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
135 135 abort: error: *certificate verify failed* (glob)
136 136 [255]
137 137 #endif
138 138
139 139 A per-host certificate matching the server's cert will be accepted
140 140
141 141 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/pub.pem" clone -U https://localhost:$HGPORT/ perhostgood1
142 142 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
143 143 requesting all changes
144 144 adding changesets
145 145 adding manifests
146 146 adding file changes
147 147 added 1 changesets with 4 changes to 4 files
148 148
149 149 A per-host certificate with multiple certs and one matching will be accepted
150 150
151 151 $ cat "$CERTSDIR/client-cert.pem" "$CERTSDIR/pub.pem" > perhost.pem
152 152 $ hg --config hostsecurity.localhost:verifycertsfile=perhost.pem clone -U https://localhost:$HGPORT/ perhostgood2
153 153 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
154 154 requesting all changes
155 155 adding changesets
156 156 adding manifests
157 157 adding file changes
158 158 added 1 changesets with 4 changes to 4 files
159 159
160 160 Defining both per-host certificate and a fingerprint will print a warning
161 161
162 162 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/pub.pem" --config hostsecurity.localhost:fingerprints=sha1:ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03 clone -U https://localhost:$HGPORT/ caandfingerwarning
163 163 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
164 164 (hostsecurity.localhost:verifycertsfile ignored when host fingerprints defined; using host fingerprints for verification)
165 165 requesting all changes
166 166 adding changesets
167 167 adding manifests
168 168 adding file changes
169 169 added 1 changesets with 4 changes to 4 files
170 170
171 171 $ DISABLECACERTS="--config devel.disableloaddefaultcerts=true"
172 172
173 173 Inability to verify peer certificate will result in abort
174 174
175 175 $ hg clone https://localhost:$HGPORT/ copy-pull $DISABLECACERTS
176 176 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
177 177 abort: unable to verify security of localhost (no loaded CA certificates); refusing to connect
178 178 (see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error or set hostsecurity.localhost:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e to trust this server)
179 179 [255]
180 180
181 181 $ hg clone --insecure https://localhost:$HGPORT/ copy-pull
182 182 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
183 183 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
184 184 requesting all changes
185 185 adding changesets
186 186 adding manifests
187 187 adding file changes
188 188 added 1 changesets with 4 changes to 4 files
189 189 updating to branch default
190 190 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
191 191 $ hg verify -R copy-pull
192 192 checking changesets
193 193 checking manifests
194 194 crosschecking files in changesets and manifests
195 195 checking files
196 196 4 files, 1 changesets, 4 total revisions
197 197 $ cd test
198 198 $ echo bar > bar
199 199 $ hg commit -A -d '1 0' -m 2
200 200 adding bar
201 201 $ cd ..
202 202
203 203 pull without cacert
204 204
205 205 $ cd copy-pull
206 206 $ cat >> .hg/hgrc <<EOF
207 207 > [hooks]
208 208 > changegroup = sh -c "printenv.py changegroup"
209 209 > EOF
210 210 $ hg pull $DISABLECACERTS
211 211 pulling from https://localhost:$HGPORT/
212 212 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
213 213 abort: unable to verify security of localhost (no loaded CA certificates); refusing to connect
214 214 (see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error or set hostsecurity.localhost:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e to trust this server)
215 215 [255]
216 216
217 217 $ hg pull --insecure
218 218 pulling from https://localhost:$HGPORT/
219 219 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
220 220 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
221 221 searching for changes
222 222 adding changesets
223 223 adding manifests
224 224 adding file changes
225 225 added 1 changesets with 1 changes to 1 files
226 226 changegroup hook: HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_NODE_LAST=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=https://localhost:$HGPORT/ (glob)
227 227 (run 'hg update' to get a working copy)
228 228 $ cd ..
229 229
230 230 cacert configured in local repo
231 231
232 232 $ cp copy-pull/.hg/hgrc copy-pull/.hg/hgrc.bu
233 233 $ echo "[web]" >> copy-pull/.hg/hgrc
234 234 $ echo "cacerts=$CERTSDIR/pub.pem" >> copy-pull/.hg/hgrc
235 235 $ hg -R copy-pull pull
236 236 pulling from https://localhost:$HGPORT/
237 237 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
238 238 searching for changes
239 239 no changes found
240 240 $ mv copy-pull/.hg/hgrc.bu copy-pull/.hg/hgrc
241 241
242 242 cacert configured globally, also testing expansion of environment
243 243 variables in the filename
244 244
245 245 $ echo "[web]" >> $HGRCPATH
246 246 $ echo 'cacerts=$P/pub.pem' >> $HGRCPATH
247 247 $ P="$CERTSDIR" hg -R copy-pull pull
248 248 pulling from https://localhost:$HGPORT/
249 249 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
250 250 searching for changes
251 251 no changes found
252 252 $ P="$CERTSDIR" hg -R copy-pull pull --insecure
253 253 pulling from https://localhost:$HGPORT/
254 254 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
255 255 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
256 256 searching for changes
257 257 no changes found
258 258
259 259 empty cacert file
260 260
261 261 $ touch emptycafile
262 262
263 263 #if sslcontext
264 264 $ hg --config web.cacerts=emptycafile -R copy-pull pull
265 265 pulling from https://localhost:$HGPORT/
266 266 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
267 267 abort: error loading CA file emptycafile: * (glob)
268 268 (file is empty or malformed?)
269 269 [255]
270 270 #else
271 271 $ hg --config web.cacerts=emptycafile -R copy-pull pull
272 272 pulling from https://localhost:$HGPORT/
273 273 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
274 274 abort: error: * (glob)
275 275 [255]
276 276 #endif
277 277
278 278 cacert mismatch
279 279
280 280 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub.pem" \
281 281 > https://$LOCALIP:$HGPORT/
282 282 pulling from https://*:$HGPORT/ (glob)
283 283 warning: connecting to $LOCALIP using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
284 284 abort: $LOCALIP certificate error: certificate is for localhost
285 285 (set hostsecurity.$LOCALIP:certfingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e config setting or use --insecure to connect insecurely)
286 286 [255]
287 287 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub.pem" \
288 288 > https://$LOCALIP:$HGPORT/ --insecure
289 289 pulling from https://*:$HGPORT/ (glob)
290 290 warning: connecting to $LOCALIP using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
291 291 warning: connection security to $LOCALIP is disabled per current settings; communication is susceptible to eavesdropping and tampering
292 292 searching for changes
293 293 no changes found
294 294 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-other.pem"
295 295 pulling from https://localhost:$HGPORT/
296 296 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
297 297 abort: error: *certificate verify failed* (glob)
298 298 [255]
299 299 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-other.pem" \
300 300 > --insecure
301 301 pulling from https://localhost:$HGPORT/
302 302 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
303 303 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
304 304 searching for changes
305 305 no changes found
306 306
307 307 Test server cert which isn't valid yet
308 308
309 309 $ hg serve -R test -p $HGPORT1 -d --pid-file=hg1.pid --certificate=server-not-yet.pem
310 310 $ cat hg1.pid >> $DAEMON_PIDS
311 311 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-not-yet.pem" \
312 312 > https://localhost:$HGPORT1/
313 313 pulling from https://localhost:$HGPORT1/
314 314 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
315 315 abort: error: *certificate verify failed* (glob)
316 316 [255]
317 317
318 318 Test server cert which no longer is valid
319 319
320 320 $ hg serve -R test -p $HGPORT2 -d --pid-file=hg2.pid --certificate=server-expired.pem
321 321 $ cat hg2.pid >> $DAEMON_PIDS
322 322 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-expired.pem" \
323 323 > https://localhost:$HGPORT2/
324 324 pulling from https://localhost:$HGPORT2/
325 325 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
326 326 abort: error: *certificate verify failed* (glob)
327 327 [255]
328 328
329 329 Disabling the TLS 1.0 warning works
330 330 $ hg -R copy-pull id https://localhost:$HGPORT/ \
331 331 > --config hostsecurity.localhost:fingerprints=sha1:ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03 \
332 332 > --config hostsecurity.disabletls10warning=true
333 333 5fed3813f7f5
334 334
335 335 #if no-sslcontext no-py27+
336 336 Setting ciphers doesn't work in Python 2.6
337 337 $ P="$CERTSDIR" hg --config hostsecurity.ciphers=HIGH -R copy-pull id https://localhost:$HGPORT/
338 338 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info
339 339 abort: setting ciphers in [hostsecurity] is not supported by this version of Python
340 340 (remove the config option or run Mercurial with a modern Python version (preferred))
341 341 [255]
342 342 #endif
343 343
344 344 Setting ciphers works in Python 2.7+ but the error message is different on
345 345 legacy ssl. We test legacy once and do more feature checking on modern
346 346 configs.
347 347
348 348 #if py27+ no-sslcontext
349 349 $ P="$CERTSDIR" hg --config hostsecurity.ciphers=invalid -R copy-pull id https://localhost:$HGPORT/
350 350 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info
351 351 abort: *No cipher can be selected. (glob)
352 352 [255]
353 353
354 354 $ P="$CERTSDIR" hg --config hostsecurity.ciphers=HIGH -R copy-pull id https://localhost:$HGPORT/
355 355 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info
356 356 5fed3813f7f5
357 357 #endif
358 358
359 359 #if sslcontext
360 360 Setting ciphers to an invalid value aborts
361 361 $ P="$CERTSDIR" hg --config hostsecurity.ciphers=invalid -R copy-pull id https://localhost:$HGPORT/
362 362 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
363 363 abort: could not set ciphers: No cipher can be selected.
364 364 (change cipher string (invalid) in config)
365 365 [255]
366 366
367 367 $ P="$CERTSDIR" hg --config hostsecurity.localhost:ciphers=invalid -R copy-pull id https://localhost:$HGPORT/
368 368 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
369 369 abort: could not set ciphers: No cipher can be selected.
370 370 (change cipher string (invalid) in config)
371 371 [255]
372 372
373 373 Changing the cipher string works
374 374
375 375 $ P="$CERTSDIR" hg --config hostsecurity.ciphers=HIGH -R copy-pull id https://localhost:$HGPORT/
376 376 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
377 377 5fed3813f7f5
378 378 #endif
379 379
380 380 Fingerprints
381 381
382 382 - works without cacerts (hostfingerprints)
383 383 $ hg -R copy-pull id https://localhost:$HGPORT/ --insecure --config hostfingerprints.localhost=ec:d8:7c:d6:b3:86:d0:4f:c1:b8:b4:1c:9d:8f:5e:16:8e:ef:1c:03
384 384 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
385 (SHA-1 fingerprint for localhost found in legacy [hostfingerprints] section; if you trust this fingerprint, set the following config value in [hostsecurity] and remove the old one from [hostfingerprints] to upgrade to a more secure SHA-256 fingerprint: localhost.fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e)
385 386 5fed3813f7f5
386 387
387 388 - works without cacerts (hostsecurity)
388 389 $ hg -R copy-pull id https://localhost:$HGPORT/ --config hostsecurity.localhost:fingerprints=sha1:ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03
389 390 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
390 391 5fed3813f7f5
391 392
392 393 $ hg -R copy-pull id https://localhost:$HGPORT/ --config hostsecurity.localhost:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e
393 394 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
394 395 5fed3813f7f5
395 396
396 397 - multiple fingerprints specified and first matches
397 398 $ hg --config 'hostfingerprints.localhost=ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03, deadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/ --insecure
398 399 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
400 (SHA-1 fingerprint for localhost found in legacy [hostfingerprints] section; if you trust this fingerprint, set the following config value in [hostsecurity] and remove the old one from [hostfingerprints] to upgrade to a more secure SHA-256 fingerprint: localhost.fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e)
399 401 5fed3813f7f5
400 402
401 403 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03, sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/
402 404 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
403 405 5fed3813f7f5
404 406
405 407 - multiple fingerprints specified and last matches
406 408 $ hg --config 'hostfingerprints.localhost=deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03' -R copy-pull id https://localhost:$HGPORT/ --insecure
407 409 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
410 (SHA-1 fingerprint for localhost found in legacy [hostfingerprints] section; if you trust this fingerprint, set the following config value in [hostsecurity] and remove the old one from [hostfingerprints] to upgrade to a more secure SHA-256 fingerprint: localhost.fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e)
408 411 5fed3813f7f5
409 412
410 413 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, sha1:ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03' -R copy-pull id https://localhost:$HGPORT/
411 414 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
412 415 5fed3813f7f5
413 416
414 417 - multiple fingerprints specified and none match
415 418
416 419 $ hg --config 'hostfingerprints.localhost=deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, aeadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/ --insecure
417 420 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
418 421 abort: certificate for localhost has unexpected fingerprint ec:d8:7c:d6:b3:86:d0:4f:c1:b8:b4:1c:9d:8f:5e:16:8e:ef:1c:03
419 422 (check hostfingerprint configuration)
420 423 [255]
421 424
422 425 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, sha1:aeadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/
423 426 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
424 427 abort: certificate for localhost has unexpected fingerprint sha1:ec:d8:7c:d6:b3:86:d0:4f:c1:b8:b4:1c:9d:8f:5e:16:8e:ef:1c:03
425 428 (check hostsecurity configuration)
426 429 [255]
427 430
428 431 - fails when cert doesn't match hostname (port is ignored)
429 432 $ hg -R copy-pull id https://localhost:$HGPORT1/ --config hostfingerprints.localhost=ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03
430 433 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
431 434 abort: certificate for localhost has unexpected fingerprint f4:2f:5a:0c:3e:52:5b:db:e7:24:a8:32:1d:18:97:6d:69:b5:87:84
432 435 (check hostfingerprint configuration)
433 436 [255]
434 437
435 438
436 439 - ignores that certificate doesn't match hostname
437 440 $ hg -R copy-pull id https://$LOCALIP:$HGPORT/ --config hostfingerprints.$LOCALIP=ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03
438 441 warning: connecting to $LOCALIP using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
442 (SHA-1 fingerprint for $LOCALIP found in legacy [hostfingerprints] section; if you trust this fingerprint, set the following config value in [hostsecurity] and remove the old one from [hostfingerprints] to upgrade to a more secure SHA-256 fingerprint: $LOCALIP.fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e)
439 443 5fed3813f7f5
440 444
441 445 Ports used by next test. Kill servers.
442 446
443 447 $ killdaemons.py hg0.pid
444 448 $ killdaemons.py hg1.pid
445 449 $ killdaemons.py hg2.pid
446 450
447 451 #if sslcontext tls1.2
448 452 Start servers running supported TLS versions
449 453
450 454 $ cd test
451 455 $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV \
452 456 > --config devel.serverexactprotocol=tls1.0
453 457 $ cat ../hg0.pid >> $DAEMON_PIDS
454 458 $ hg serve -p $HGPORT1 -d --pid-file=../hg1.pid --certificate=$PRIV \
455 459 > --config devel.serverexactprotocol=tls1.1
456 460 $ cat ../hg1.pid >> $DAEMON_PIDS
457 461 $ hg serve -p $HGPORT2 -d --pid-file=../hg2.pid --certificate=$PRIV \
458 462 > --config devel.serverexactprotocol=tls1.2
459 463 $ cat ../hg2.pid >> $DAEMON_PIDS
460 464 $ cd ..
461 465
462 466 Clients talking same TLS versions work
463 467
464 468 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.0 id https://localhost:$HGPORT/
465 469 5fed3813f7f5
466 470 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.1 id https://localhost:$HGPORT1/
467 471 5fed3813f7f5
468 472 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.2 id https://localhost:$HGPORT2/
469 473 5fed3813f7f5
470 474
471 475 Clients requiring newer TLS version than what server supports fail
472 476
473 477 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/
474 478 (could not negotiate a common security protocol (tls1.1+) with localhost; the likely cause is Mercurial is configured to be more secure than the server can support)
475 479 (consider contacting the operator of this server and ask them to support modern TLS protocol versions; or, set hostsecurity.localhost:minimumprotocol=tls1.0 to allow use of legacy, less secure protocols when communicating with this server)
476 480 (see https://mercurial-scm.org/wiki/SecureConnections for more info)
477 481 abort: error: *unsupported protocol* (glob)
478 482 [255]
479 483
480 484 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.1 id https://localhost:$HGPORT/
481 485 (could not negotiate a common security protocol (tls1.1+) with localhost; the likely cause is Mercurial is configured to be more secure than the server can support)
482 486 (consider contacting the operator of this server and ask them to support modern TLS protocol versions; or, set hostsecurity.localhost:minimumprotocol=tls1.0 to allow use of legacy, less secure protocols when communicating with this server)
483 487 (see https://mercurial-scm.org/wiki/SecureConnections for more info)
484 488 abort: error: *unsupported protocol* (glob)
485 489 [255]
486 490 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.2 id https://localhost:$HGPORT/
487 491 (could not negotiate a common security protocol (tls1.2+) with localhost; the likely cause is Mercurial is configured to be more secure than the server can support)
488 492 (consider contacting the operator of this server and ask them to support modern TLS protocol versions; or, set hostsecurity.localhost:minimumprotocol=tls1.0 to allow use of legacy, less secure protocols when communicating with this server)
489 493 (see https://mercurial-scm.org/wiki/SecureConnections for more info)
490 494 abort: error: *unsupported protocol* (glob)
491 495 [255]
492 496 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.2 id https://localhost:$HGPORT1/
493 497 (could not negotiate a common security protocol (tls1.2+) with localhost; the likely cause is Mercurial is configured to be more secure than the server can support)
494 498 (consider contacting the operator of this server and ask them to support modern TLS protocol versions; or, set hostsecurity.localhost:minimumprotocol=tls1.0 to allow use of legacy, less secure protocols when communicating with this server)
495 499 (see https://mercurial-scm.org/wiki/SecureConnections for more info)
496 500 abort: error: *unsupported protocol* (glob)
497 501 [255]
498 502
499 503 --insecure will allow TLS 1.0 connections and override configs
500 504
501 505 $ hg --config hostsecurity.minimumprotocol=tls1.2 id --insecure https://localhost:$HGPORT1/
502 506 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
503 507 5fed3813f7f5
504 508
505 509 The per-host config option overrides the default
506 510
507 511 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
508 512 > --config hostsecurity.minimumprotocol=tls1.2 \
509 513 > --config hostsecurity.localhost:minimumprotocol=tls1.0
510 514 5fed3813f7f5
511 515
512 516 The per-host config option by itself works
513 517
514 518 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
515 519 > --config hostsecurity.localhost:minimumprotocol=tls1.2
516 520 (could not negotiate a common security protocol (tls1.2+) with localhost; the likely cause is Mercurial is configured to be more secure than the server can support)
517 521 (consider contacting the operator of this server and ask them to support modern TLS protocol versions; or, set hostsecurity.localhost:minimumprotocol=tls1.0 to allow use of legacy, less secure protocols when communicating with this server)
518 522 (see https://mercurial-scm.org/wiki/SecureConnections for more info)
519 523 abort: error: *unsupported protocol* (glob)
520 524 [255]
521 525
522 526 .hg/hgrc file [hostsecurity] settings are applied to remote ui instances (issue5305)
523 527
524 528 $ cat >> copy-pull/.hg/hgrc << EOF
525 529 > [hostsecurity]
526 530 > localhost:minimumprotocol=tls1.2
527 531 > EOF
528 532 $ P="$CERTSDIR" hg -R copy-pull id https://localhost:$HGPORT/
529 533 (could not negotiate a common security protocol (tls1.2+) with localhost; the likely cause is Mercurial is configured to be more secure than the server can support)
530 534 (consider contacting the operator of this server and ask them to support modern TLS protocol versions; or, set hostsecurity.localhost:minimumprotocol=tls1.0 to allow use of legacy, less secure protocols when communicating with this server)
531 535 (see https://mercurial-scm.org/wiki/SecureConnections for more info)
532 536 abort: error: *unsupported protocol* (glob)
533 537 [255]
534 538
535 539 $ killdaemons.py hg0.pid
536 540 $ killdaemons.py hg1.pid
537 541 $ killdaemons.py hg2.pid
538 542 #endif
539 543
540 544 Prepare for connecting through proxy
541 545
542 546 $ hg serve -R test -p $HGPORT -d --pid-file=hg0.pid --certificate=$PRIV
543 547 $ cat hg0.pid >> $DAEMON_PIDS
544 548 $ hg serve -R test -p $HGPORT2 -d --pid-file=hg2.pid --certificate=server-expired.pem
545 549 $ cat hg2.pid >> $DAEMON_PIDS
546 550 tinyproxy.py doesn't fully detach, so killing it may result in extra output
547 551 from the shell. So don't kill it.
548 552 $ tinyproxy.py $HGPORT1 localhost >proxy.log </dev/null 2>&1 &
549 553 $ while [ ! -f proxy.pid ]; do sleep 0; done
550 554 $ cat proxy.pid >> $DAEMON_PIDS
551 555
552 556 $ echo "[http_proxy]" >> copy-pull/.hg/hgrc
553 557 $ echo "always=True" >> copy-pull/.hg/hgrc
554 558 $ echo "[hostfingerprints]" >> copy-pull/.hg/hgrc
555 559 $ echo "localhost =" >> copy-pull/.hg/hgrc
556 560
557 561 Test unvalidated https through proxy
558 562
559 563 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --insecure
560 564 pulling from https://localhost:$HGPORT/
561 565 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
562 566 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
563 567 searching for changes
564 568 no changes found
565 569
566 570 Test https with cacert and fingerprint through proxy
567 571
568 572 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull \
569 573 > --config web.cacerts="$CERTSDIR/pub.pem"
570 574 pulling from https://localhost:$HGPORT/
571 575 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
572 576 searching for changes
573 577 no changes found
574 578 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull https://localhost:$HGPORT/ --config hostfingerprints.localhost=ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03 --trace
575 579 pulling from https://*:$HGPORT/ (glob)
576 580 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
581 (SHA-1 fingerprint for localhost found in legacy [hostfingerprints] section; if you trust this fingerprint, set the following config value in [hostsecurity] and remove the old one from [hostfingerprints] to upgrade to a more secure SHA-256 fingerprint: localhost.fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e)
577 582 searching for changes
578 583 no changes found
579 584
580 585 Test https with cert problems through proxy
581 586
582 587 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull \
583 588 > --config web.cacerts="$CERTSDIR/pub-other.pem"
584 589 pulling from https://localhost:$HGPORT/
585 590 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
586 591 abort: error: *certificate verify failed* (glob)
587 592 [255]
588 593 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull \
589 594 > --config web.cacerts="$CERTSDIR/pub-expired.pem" https://localhost:$HGPORT2/
590 595 pulling from https://localhost:$HGPORT2/
591 596 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
592 597 abort: error: *certificate verify failed* (glob)
593 598 [255]
594 599
595 600
596 601 $ killdaemons.py hg0.pid
597 602
598 603 #if sslcontext
599 604
600 605 Start hgweb that requires client certificates:
601 606
602 607 $ cd test
603 608 $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV \
604 609 > --config devel.servercafile=$PRIV --config devel.serverrequirecert=true
605 610 $ cat ../hg0.pid >> $DAEMON_PIDS
606 611 $ cd ..
607 612
608 613 without client certificate:
609 614
610 615 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/
611 616 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
612 617 abort: error: *handshake failure* (glob)
613 618 [255]
614 619
615 620 with client certificate:
616 621
617 622 $ cat << EOT >> $HGRCPATH
618 623 > [auth]
619 624 > l.prefix = localhost
620 625 > l.cert = $CERTSDIR/client-cert.pem
621 626 > l.key = $CERTSDIR/client-key.pem
622 627 > EOT
623 628
624 629 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
625 630 > --config auth.l.key="$CERTSDIR/client-key-decrypted.pem"
626 631 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
627 632 5fed3813f7f5
628 633
629 634 $ printf '1234\n' | env P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
630 635 > --config ui.interactive=True --config ui.nontty=True
631 636 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
632 637 passphrase for */client-key.pem: 5fed3813f7f5 (glob)
633 638
634 639 $ env P="$CERTSDIR" hg id https://localhost:$HGPORT/
635 640 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
636 641 abort: error: * (glob)
637 642 [255]
638 643
639 644 #endif
General Comments 0
You need to be logged in to leave comments. Login now