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