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