comparison mercurial/sslutil.py @ 29601:6cff2ac0ccb9

sslutil: more robustly detect protocol support The Python ssl module conditionally sets the TLS 1.1 and TLS 1.2 constants depending on whether HAVE_TLSv1_2 is defined. Yes, these are both tied to the same constant (I would think there would be separate constants for each version). Perhaps support for TLS 1.1 and 1.2 were added at the same time and the assumption is that OpenSSL either has neither or both. I don't know. As part of developing this patch, it was discovered that Apple's /usr/bin/python2.7 does not support TLS 1.1 and 1.2 (only TLS 1.0)! On OS X 10.11, Apple Python has the modern ssl module including SSLContext, but it doesn't appear to negotiate TLS 1.1+ nor does it expose the constants related to TLS 1.1+. Since this code is doing more robust feature detection (and not assuming modern ssl implies TLS 1.1+ support), we now get TLS 1.0 warnings when running on Apple Python. Hence the test changes. I'm not super thrilled about shipping a Mercurial that always whines about TLS 1.0 on OS X. We may want a follow-up patch to suppress this warning.
author Gregory Szorc <gregory.szorc@gmail.com>
date Mon, 18 Jul 2016 11:27:27 -0700
parents 4a4b8d3b4e43
children 2960ceee1948
comparison
equal deleted inserted replaced
29600:7a157639b8f2 29601:6cff2ac0ccb9
34 'tls1.1', 34 'tls1.1',
35 'tls1.2', 35 'tls1.2',
36 ]) 36 ])
37 37
38 hassni = getattr(ssl, 'HAS_SNI', False) 38 hassni = getattr(ssl, 'HAS_SNI', False)
39
40 # TLS 1.1 and 1.2 may not be supported if the OpenSSL Python is compiled
41 # against doesn't support them.
42 supportedprotocols = set(['tls1.0'])
43 if util.safehasattr(ssl, 'PROTOCOL_TLSv1_1'):
44 supportedprotocols.add('tls1.1')
45 if util.safehasattr(ssl, 'PROTOCOL_TLSv1_2'):
46 supportedprotocols.add('tls1.2')
39 47
40 try: 48 try:
41 # ssl.SSLContext was added in 2.7.9 and presence indicates modern 49 # ssl.SSLContext was added in 2.7.9 and presence indicates modern
42 # SSL/TLS features are available. 50 # SSL/TLS features are available.
43 SSLContext = ssl.SSLContext 51 SSLContext = ssl.SSLContext
146 _('unsupported protocol from hostsecurity.%s: %s') % 154 _('unsupported protocol from hostsecurity.%s: %s') %
147 (key, protocol), 155 (key, protocol),
148 hint=_('valid protocols: %s') % 156 hint=_('valid protocols: %s') %
149 ' '.join(sorted(configprotocols))) 157 ' '.join(sorted(configprotocols)))
150 158
151 # Legacy Python can only do TLS 1.0. We default to TLS 1.1+ where we 159 # We default to TLS 1.1+ where we can because TLS 1.0 has known
152 # can because TLS 1.0 has known vulnerabilities (like BEAST and POODLE). 160 # vulnerabilities (like BEAST and POODLE). We allow users to downgrade to
153 # We allow users to downgrade to TLS 1.0+ via config options in case a 161 # TLS 1.0+ via config options in case a legacy server is encountered.
154 # legacy server is encountered. 162 if 'tls1.1' in supportedprotocols:
155 if modernssl:
156 defaultprotocol = 'tls1.1' 163 defaultprotocol = 'tls1.1'
157 else: 164 else:
158 # Let people on legacy Python versions know they are borderline 165 # Let people know they are borderline secure.
159 # secure.
160 # We don't document this config option because we want people to see 166 # We don't document this config option because we want people to see
161 # the bold warnings on the web site. 167 # the bold warnings on the web site.
162 # internal config: hostsecurity.disabletls10warning 168 # internal config: hostsecurity.disabletls10warning
163 if not ui.configbool('hostsecurity', 'disabletls10warning'): 169 if not ui.configbool('hostsecurity', 'disabletls10warning'):
164 ui.warn(_('warning: connecting to %s using legacy security ' 170 ui.warn(_('warning: connecting to %s using legacy security '
286 # only (as opposed to multiple versions). So the method for 292 # only (as opposed to multiple versions). So the method for
287 # supporting multiple TLS versions is to use PROTOCOL_SSLv23 and 293 # supporting multiple TLS versions is to use PROTOCOL_SSLv23 and
288 # disable protocols via SSLContext.options and OP_NO_* constants. 294 # disable protocols via SSLContext.options and OP_NO_* constants.
289 # However, SSLContext.options doesn't work unless we have the 295 # However, SSLContext.options doesn't work unless we have the
290 # full/real SSLContext available to us. 296 # full/real SSLContext available to us.
291 if not modernssl: 297 if supportedprotocols == set(['tls1.0']):
292 if protocol != 'tls1.0': 298 if protocol != 'tls1.0':
293 raise error.Abort(_('current Python does not support protocol ' 299 raise error.Abort(_('current Python does not support protocol '
294 'setting %s') % protocol, 300 'setting %s') % protocol,
295 hint=_('upgrade Python or disable setting since ' 301 hint=_('upgrade Python or disable setting since '
296 'only TLS 1.0 is supported')) 302 'only TLS 1.0 is supported'))
439 # footgun to kill security. Don't define it. 445 # footgun to kill security. Don't define it.
440 exactprotocol = ui.config('devel', 'serverexactprotocol') 446 exactprotocol = ui.config('devel', 'serverexactprotocol')
441 if exactprotocol == 'tls1.0': 447 if exactprotocol == 'tls1.0':
442 protocol = ssl.PROTOCOL_TLSv1 448 protocol = ssl.PROTOCOL_TLSv1
443 elif exactprotocol == 'tls1.1': 449 elif exactprotocol == 'tls1.1':
450 if 'tls1.1' not in supportedprotocols:
451 raise error.Abort(_('TLS 1.1 not supported by this Python'))
444 protocol = ssl.PROTOCOL_TLSv1_1 452 protocol = ssl.PROTOCOL_TLSv1_1
445 elif exactprotocol == 'tls1.2': 453 elif exactprotocol == 'tls1.2':
454 if 'tls1.2' not in supportedprotocols:
455 raise error.Abort(_('TLS 1.2 not supported by this Python'))
446 protocol = ssl.PROTOCOL_TLSv1_2 456 protocol = ssl.PROTOCOL_TLSv1_2
447 elif exactprotocol: 457 elif exactprotocol:
448 raise error.Abort(_('invalid value for serverexactprotocol: %s') % 458 raise error.Abort(_('invalid value for serverexactprotocol: %s') %
449 exactprotocol) 459 exactprotocol)
450 460