# HG changeset patch # User Julien Cristau # Date 1649506532 -7200 # Node ID 5144d3579a9cd1491013fde0ba5c3a7ea6821b5f # Parent 7d1daa1ef28664f4233393d8ae4c842026e12515 sslutil: avoid deprecation warnings from python 3.10's ssl module Use ssl.PROTOCOL_TLS_{CLIENT,SERVER} and SSLContext.{min,max}imum_version when supported (3.7+). And, catch deprecation warnings when the user asks for deprecated TLS versions (1.0 and 1.1). Differential Revision: https://phab.mercurial-scm.org/D12488 diff -r 7d1daa1ef286 -r 5144d3579a9c mercurial/sslutil.py --- a/mercurial/sslutil.py Wed Apr 06 22:29:49 2022 +0200 +++ b/mercurial/sslutil.py Sat Apr 09 14:15:32 2022 +0200 @@ -12,6 +12,7 @@ import os import re import ssl +import warnings from .i18n import _ from .pycompat import getattr @@ -308,12 +309,43 @@ # bundle with a specific CA cert removed. If the system/default CA bundle # is loaded and contains that removed CA, you've just undone the user's # choice. - # - # Despite its name, PROTOCOL_SSLv23 selects the highest protocol that both - # ends support, including TLS protocols. commonssloptions() restricts the - # set of allowed protocols. - sslcontext = ssl.SSLContext(ssl.PROTOCOL_SSLv23) - sslcontext.options |= commonssloptions(settings[b'minimumprotocol']) + + if util.safehasattr(ssl, 'PROTOCOL_TLS_CLIENT'): + # python 3.7+ + sslcontext = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + minimumprotocol = settings[b'minimumprotocol'] + if minimumprotocol == b'tls1.0': + with warnings.catch_warnings(): + warnings.filterwarnings( + 'ignore', + 'ssl.TLSVersion.TLSv1 is deprecated', + DeprecationWarning, + ) + sslcontext.minimum_version = ssl.TLSVersion.TLSv1 + elif minimumprotocol == b'tls1.1': + with warnings.catch_warnings(): + warnings.filterwarnings( + 'ignore', + 'ssl.TLSVersion.TLSv1_1 is deprecated', + DeprecationWarning, + ) + sslcontext.minimum_version = ssl.TLSVersion.TLSv1_1 + elif minimumprotocol == b'tls1.2': + sslcontext.minimum_version = ssl.TLSVersion.TLSv1_2 + else: + raise error.Abort(_(b'this should not happen')) + # Prevent CRIME. + # There is no guarantee this attribute is defined on the module. + sslcontext.options |= getattr(ssl, 'OP_NO_COMPRESSION', 0) + else: + # Despite its name, PROTOCOL_SSLv23 selects the highest protocol that both + # ends support, including TLS protocols. commonssloptions() restricts the + # set of allowed protocols. + sslcontext = ssl.SSLContext(ssl.PROTOCOL_SSLv23) + sslcontext.options |= commonssloptions(settings[b'minimumprotocol']) + + # We check the hostname ourselves in _verifycert + sslcontext.check_hostname = False sslcontext.verify_mode = settings[b'verifymode'] if settings[b'ciphers']: @@ -509,37 +541,77 @@ _(b'referenced certificate file (%s) does not exist') % f ) - # Despite its name, PROTOCOL_SSLv23 selects the highest protocol that both - # ends support, including TLS protocols. commonssloptions() restricts the - # set of allowed protocols. - protocol = ssl.PROTOCOL_SSLv23 - options = commonssloptions(b'tls1.0') + if util.safehasattr(ssl, 'PROTOCOL_TLS_SERVER'): + # python 3.7+ + sslcontext = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + sslcontext.options |= getattr(ssl, 'OP_NO_COMPRESSION', 0) - # This config option is intended for use in tests only. It is a giant - # footgun to kill security. Don't define it. - exactprotocol = ui.config(b'devel', b'serverexactprotocol') - if exactprotocol == b'tls1.0': - if b'tls1.0' not in supportedprotocols: - raise error.Abort(_(b'TLS 1.0 not supported by this Python')) - protocol = ssl.PROTOCOL_TLSv1 - elif exactprotocol == b'tls1.1': - if b'tls1.1' not in supportedprotocols: - raise error.Abort(_(b'TLS 1.1 not supported by this Python')) - protocol = ssl.PROTOCOL_TLSv1_1 - elif exactprotocol == b'tls1.2': - if b'tls1.2' not in supportedprotocols: - raise error.Abort(_(b'TLS 1.2 not supported by this Python')) - protocol = ssl.PROTOCOL_TLSv1_2 - elif exactprotocol: - raise error.Abort( - _(b'invalid value for serverexactprotocol: %s') % exactprotocol - ) + # This config option is intended for use in tests only. It is a giant + # footgun to kill security. Don't define it. + exactprotocol = ui.config(b'devel', b'serverexactprotocol') + if exactprotocol == b'tls1.0': + if b'tls1.0' not in supportedprotocols: + raise error.Abort(_(b'TLS 1.0 not supported by this Python')) + with warnings.catch_warnings(): + warnings.filterwarnings( + 'ignore', + 'ssl.TLSVersion.TLSv1 is deprecated', + DeprecationWarning, + ) + sslcontext.minimum_version = ssl.TLSVersion.TLSv1 + sslcontext.maximum_version = ssl.TLSVersion.TLSv1 + elif exactprotocol == b'tls1.1': + if b'tls1.1' not in supportedprotocols: + raise error.Abort(_(b'TLS 1.1 not supported by this Python')) + with warnings.catch_warnings(): + warnings.filterwarnings( + 'ignore', + 'ssl.TLSVersion.TLSv1_1 is deprecated', + DeprecationWarning, + ) + sslcontext.minimum_version = ssl.TLSVersion.TLSv1_1 + sslcontext.maximum_version = ssl.TLSVersion.TLSv1_1 + elif exactprotocol == b'tls1.2': + if b'tls1.2' not in supportedprotocols: + raise error.Abort(_(b'TLS 1.2 not supported by this Python')) + sslcontext.minimum_version = ssl.TLSVersion.TLSv1_2 + sslcontext.maximum_version = ssl.TLSVersion.TLSv1_2 + elif exactprotocol: + raise error.Abort( + _(b'invalid value for serverexactprotocol: %s') % exactprotocol + ) + else: + # Despite its name, PROTOCOL_SSLv23 selects the highest protocol that both + # ends support, including TLS protocols. commonssloptions() restricts the + # set of allowed protocols. + protocol = ssl.PROTOCOL_SSLv23 + options = commonssloptions(b'tls1.0') - # We /could/ use create_default_context() here since it doesn't load - # CAs when configured for client auth. However, it is hard-coded to - # use ssl.PROTOCOL_SSLv23 which may not be appropriate here. - sslcontext = ssl.SSLContext(protocol) - sslcontext.options |= options + # This config option is intended for use in tests only. It is a giant + # footgun to kill security. Don't define it. + exactprotocol = ui.config(b'devel', b'serverexactprotocol') + if exactprotocol == b'tls1.0': + if b'tls1.0' not in supportedprotocols: + raise error.Abort(_(b'TLS 1.0 not supported by this Python')) + protocol = ssl.PROTOCOL_TLSv1 + elif exactprotocol == b'tls1.1': + if b'tls1.1' not in supportedprotocols: + raise error.Abort(_(b'TLS 1.1 not supported by this Python')) + protocol = ssl.PROTOCOL_TLSv1_1 + elif exactprotocol == b'tls1.2': + if b'tls1.2' not in supportedprotocols: + raise error.Abort(_(b'TLS 1.2 not supported by this Python')) + protocol = ssl.PROTOCOL_TLSv1_2 + elif exactprotocol: + raise error.Abort( + _(b'invalid value for serverexactprotocol: %s') % exactprotocol + ) + + # We /could/ use create_default_context() here since it doesn't load + # CAs when configured for client auth. However, it is hard-coded to + # use ssl.PROTOCOL_SSLv23 which may not be appropriate here. + sslcontext = ssl.SSLContext(protocol) + sslcontext.options |= options # Improve forward secrecy. sslcontext.options |= getattr(ssl, 'OP_SINGLE_DH_USE', 0)