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