27 # |
27 # |
28 # Depending on the version of Python being used, SSL/TLS support is either |
28 # Depending on the version of Python being used, SSL/TLS support is either |
29 # modern/secure or legacy/insecure. Many operations in this module have |
29 # modern/secure or legacy/insecure. Many operations in this module have |
30 # separate code paths depending on support in Python. |
30 # separate code paths depending on support in Python. |
31 |
31 |
|
32 configprotocols = set([ |
|
33 'tls1.0', |
|
34 'tls1.1', |
|
35 'tls1.2', |
|
36 ]) |
|
37 |
32 hassni = getattr(ssl, 'HAS_SNI', False) |
38 hassni = getattr(ssl, 'HAS_SNI', False) |
33 |
|
34 try: |
|
35 OP_NO_SSLv2 = ssl.OP_NO_SSLv2 |
|
36 OP_NO_SSLv3 = ssl.OP_NO_SSLv3 |
|
37 except AttributeError: |
|
38 OP_NO_SSLv2 = 0x1000000 |
|
39 OP_NO_SSLv3 = 0x2000000 |
|
40 |
39 |
41 try: |
40 try: |
42 # ssl.SSLContext was added in 2.7.9 and presence indicates modern |
41 # ssl.SSLContext was added in 2.7.9 and presence indicates modern |
43 # SSL/TLS features are available. |
42 # SSL/TLS features are available. |
44 SSLContext = ssl.SSLContext |
43 SSLContext = ssl.SSLContext |
134 'ctxoptions': None, |
133 'ctxoptions': None, |
135 } |
134 } |
136 |
135 |
137 # Despite its name, PROTOCOL_SSLv23 selects the highest protocol |
136 # Despite its name, PROTOCOL_SSLv23 selects the highest protocol |
138 # that both ends support, including TLS protocols. On legacy stacks, |
137 # that both ends support, including TLS protocols. On legacy stacks, |
139 # the highest it likely goes in TLS 1.0. On modern stacks, it can |
138 # the highest it likely goes is TLS 1.0. On modern stacks, it can |
140 # support TLS 1.2. |
139 # support TLS 1.2. |
141 # |
140 # |
142 # The PROTOCOL_TLSv* constants select a specific TLS version |
141 # The PROTOCOL_TLSv* constants select a specific TLS version |
143 # only (as opposed to multiple versions). So the method for |
142 # only (as opposed to multiple versions). So the method for |
144 # supporting multiple TLS versions is to use PROTOCOL_SSLv23 and |
143 # supporting multiple TLS versions is to use PROTOCOL_SSLv23 and |
145 # disable protocols via SSLContext.options and OP_NO_* constants. |
144 # disable protocols via SSLContext.options and OP_NO_* constants. |
146 # However, SSLContext.options doesn't work unless we have the |
145 # However, SSLContext.options doesn't work unless we have the |
147 # full/real SSLContext available to us. |
146 # full/real SSLContext available to us. |
148 if modernssl: |
147 |
149 s['protocol'] = ssl.PROTOCOL_SSLv23 |
148 # Allow minimum TLS protocol to be specified in the config. |
150 else: |
149 def validateprotocol(protocol, key): |
151 s['protocol'] = ssl.PROTOCOL_TLSv1 |
150 if protocol not in configprotocols: |
152 |
151 raise error.Abort( |
153 # SSLv2 and SSLv3 are broken. We ban them outright. |
152 _('unsupported protocol from hostsecurity.%s: %s') % |
154 # WARNING: ctxoptions doesn't have an effect unless the modern ssl module |
153 (key, protocol), |
155 # is available. Be careful when adding flags! |
154 hint=_('valid protocols: %s') % |
156 s['ctxoptions'] = OP_NO_SSLv2 | OP_NO_SSLv3 |
155 ' '.join(sorted(configprotocols))) |
157 |
156 |
158 # Prevent CRIME. |
157 key = 'minimumprotocol' |
159 # There is no guarantee this attribute is defined on the module. |
158 # Default to TLS 1.0+ as that is what browsers are currently doing. |
160 s['ctxoptions'] |= getattr(ssl, 'OP_NO_COMPRESSION', 0) |
159 protocol = ui.config('hostsecurity', key, 'tls1.0') |
|
160 validateprotocol(protocol, key) |
|
161 |
|
162 key = '%s:minimumprotocol' % hostname |
|
163 protocol = ui.config('hostsecurity', key, protocol) |
|
164 validateprotocol(protocol, key) |
|
165 |
|
166 s['protocol'], s['ctxoptions'] = protocolsettings(protocol) |
161 |
167 |
162 # Look for fingerprints in [hostsecurity] section. Value is a list |
168 # Look for fingerprints in [hostsecurity] section. Value is a list |
163 # of <alg>:<fingerprint> strings. |
169 # of <alg>:<fingerprint> strings. |
164 fingerprints = ui.configlist('hostsecurity', '%s:fingerprints' % hostname, |
170 fingerprints = ui.configlist('hostsecurity', '%s:fingerprints' % hostname, |
165 []) |
171 []) |
248 assert s['ctxoptions'] is not None |
254 assert s['ctxoptions'] is not None |
249 assert s['verifymode'] is not None |
255 assert s['verifymode'] is not None |
250 |
256 |
251 return s |
257 return s |
252 |
258 |
|
259 def protocolsettings(protocol): |
|
260 """Resolve the protocol and context options for a config value.""" |
|
261 if protocol not in configprotocols: |
|
262 raise ValueError('protocol value not supported: %s' % protocol) |
|
263 |
|
264 # Legacy ssl module only supports up to TLS 1.0. Ideally we'd use |
|
265 # PROTOCOL_SSLv23 and options to disable SSLv2 and SSLv3. However, |
|
266 # SSLContext.options doesn't work in our implementation since we use |
|
267 # a fake SSLContext on these Python versions. |
|
268 if not modernssl: |
|
269 if protocol != 'tls1.0': |
|
270 raise error.Abort(_('current Python does not support protocol ' |
|
271 'setting %s') % protocol, |
|
272 hint=_('upgrade Python or disable setting since ' |
|
273 'only TLS 1.0 is supported')) |
|
274 |
|
275 return ssl.PROTOCOL_TLSv1, 0 |
|
276 |
|
277 # WARNING: returned options don't work unless the modern ssl module |
|
278 # is available. Be careful when adding options here. |
|
279 |
|
280 # SSLv2 and SSLv3 are broken. We ban them outright. |
|
281 options = ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 |
|
282 |
|
283 if protocol == 'tls1.0': |
|
284 # Defaults above are to use TLS 1.0+ |
|
285 pass |
|
286 elif protocol == 'tls1.1': |
|
287 options |= ssl.OP_NO_TLSv1 |
|
288 elif protocol == 'tls1.2': |
|
289 options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1 |
|
290 else: |
|
291 raise error.Abort(_('this should not happen')) |
|
292 |
|
293 # Prevent CRIME. |
|
294 # There is no guarantee this attribute is defined on the module. |
|
295 options |= getattr(ssl, 'OP_NO_COMPRESSION', 0) |
|
296 |
|
297 return ssl.PROTOCOL_SSLv23, options |
|
298 |
253 def wrapsocket(sock, keyfile, certfile, ui, serverhostname=None): |
299 def wrapsocket(sock, keyfile, certfile, ui, serverhostname=None): |
254 """Add SSL/TLS to a socket. |
300 """Add SSL/TLS to a socket. |
255 |
301 |
256 This is a glorified wrapper for ``ssl.wrap_socket()``. It makes sane |
302 This is a glorified wrapper for ``ssl.wrap_socket()``. It makes sane |
257 choices based on what security options are available. |
303 choices based on what security options are available. |
304 else: |
350 else: |
305 caloaded = False |
351 caloaded = False |
306 |
352 |
307 try: |
353 try: |
308 sslsocket = sslcontext.wrap_socket(sock, server_hostname=serverhostname) |
354 sslsocket = sslcontext.wrap_socket(sock, server_hostname=serverhostname) |
309 except ssl.SSLError: |
355 except ssl.SSLError as e: |
310 # If we're doing certificate verification and no CA certs are loaded, |
356 # If we're doing certificate verification and no CA certs are loaded, |
311 # that is almost certainly the reason why verification failed. Provide |
357 # that is almost certainly the reason why verification failed. Provide |
312 # a hint to the user. |
358 # a hint to the user. |
313 # Only modern ssl module exposes SSLContext.get_ca_certs() so we can |
359 # Only modern ssl module exposes SSLContext.get_ca_certs() so we can |
314 # only show this warning if modern ssl is available. |
360 # only show this warning if modern ssl is available. |
316 modernssl and not sslcontext.get_ca_certs()): |
362 modernssl and not sslcontext.get_ca_certs()): |
317 ui.warn(_('(an attempt was made to load CA certificates but none ' |
363 ui.warn(_('(an attempt was made to load CA certificates but none ' |
318 'were loaded; see ' |
364 'were loaded; see ' |
319 'https://mercurial-scm.org/wiki/SecureConnections for ' |
365 'https://mercurial-scm.org/wiki/SecureConnections for ' |
320 'how to configure Mercurial to avoid this error)\n')) |
366 'how to configure Mercurial to avoid this error)\n')) |
|
367 # Try to print more helpful error messages for known failures. |
|
368 if util.safehasattr(e, 'reason'): |
|
369 if e.reason == 'UNSUPPORTED_PROTOCOL': |
|
370 ui.warn(_('(could not negotiate a common protocol; see ' |
|
371 'https://mercurial-scm.org/wiki/SecureConnections ' |
|
372 'for how to configure Mercurial to avoid this ' |
|
373 'error)\n')) |
321 raise |
374 raise |
322 |
375 |
323 # check if wrap_socket failed silently because socket had been |
376 # check if wrap_socket failed silently because socket had been |
324 # closed |
377 # closed |
325 # - see http://bugs.python.org/issue13721 |
378 # - see http://bugs.python.org/issue13721 |
347 |
400 |
348 ``requireclientcert`` specifies whether to require client certificates. |
401 ``requireclientcert`` specifies whether to require client certificates. |
349 |
402 |
350 Typically ``cafile`` is only defined if ``requireclientcert`` is true. |
403 Typically ``cafile`` is only defined if ``requireclientcert`` is true. |
351 """ |
404 """ |
|
405 protocol, options = protocolsettings('tls1.0') |
|
406 |
|
407 # This config option is intended for use in tests only. It is a giant |
|
408 # footgun to kill security. Don't define it. |
|
409 exactprotocol = ui.config('devel', 'serverexactprotocol') |
|
410 if exactprotocol == 'tls1.0': |
|
411 protocol = ssl.PROTOCOL_TLSv1 |
|
412 elif exactprotocol == 'tls1.1': |
|
413 protocol = ssl.PROTOCOL_TLSv1_1 |
|
414 elif exactprotocol == 'tls1.2': |
|
415 protocol = ssl.PROTOCOL_TLSv1_2 |
|
416 elif exactprotocol: |
|
417 raise error.Abort(_('invalid value for serverexactprotocol: %s') % |
|
418 exactprotocol) |
|
419 |
352 if modernssl: |
420 if modernssl: |
353 # We /could/ use create_default_context() here since it doesn't load |
421 # We /could/ use create_default_context() here since it doesn't load |
354 # CAs when configured for client auth. |
422 # CAs when configured for client auth. However, it is hard-coded to |
355 sslcontext = SSLContext(ssl.PROTOCOL_SSLv23) |
423 # use ssl.PROTOCOL_SSLv23 which may not be appropriate here. |
356 # SSLv2 and SSLv3 are broken. Ban them outright. |
424 sslcontext = SSLContext(protocol) |
357 sslcontext.options |= OP_NO_SSLv2 | OP_NO_SSLv3 |
425 sslcontext.options |= options |
358 # Prevent CRIME |
426 |
359 sslcontext.options |= getattr(ssl, 'OP_NO_COMPRESSION', 0) |
|
360 # Improve forward secrecy. |
427 # Improve forward secrecy. |
361 sslcontext.options |= getattr(ssl, 'OP_SINGLE_DH_USE', 0) |
428 sslcontext.options |= getattr(ssl, 'OP_SINGLE_DH_USE', 0) |
362 sslcontext.options |= getattr(ssl, 'OP_SINGLE_ECDH_USE', 0) |
429 sslcontext.options |= getattr(ssl, 'OP_SINGLE_ECDH_USE', 0) |
363 |
430 |
364 # Use the list of more secure ciphers if found in the ssl module. |
431 # Use the list of more secure ciphers if found in the ssl module. |