diff --git a/mercurial/help/config.txt b/mercurial/help/config.txt --- a/mercurial/help/config.txt +++ b/mercurial/help/config.txt @@ -1005,6 +1005,18 @@ other machines. The following options control default behavior for all hosts. +``ciphers`` + Defines the cryptographic ciphers to use for connections. + + Value must be a valid OpenSSL Cipher List Format as documented at + https://www.openssl.org/docs/manmaster/apps/ciphers.html#CIPHER-LIST-FORMAT. + + This setting is for advanced users only. Setting to incorrect values + can significantly lower connection security or decrease performance. + You have been warned. + + This option requires Python 2.7. + ``minimumprotocol`` Defines the minimum channel encryption protocol to use. @@ -1027,6 +1039,10 @@ per-host basis. The following per-host settings can be defined. +``ciphers`` + This behaves like ``ciphers`` as described above except it only applies + to the host on which it is defined. + ``fingerprints`` A list of hashes of the DER encoded peer/remote certificate. Values have the form ``algorithm``:``fingerprint``. e.g. diff --git a/mercurial/sslutil.py b/mercurial/sslutil.py --- a/mercurial/sslutil.py +++ b/mercurial/sslutil.py @@ -84,7 +84,11 @@ except AttributeError: def set_ciphers(self, ciphers): if not self._supportsciphers: - raise error.Abort(_('setting ciphers not supported')) + raise error.Abort(_('setting ciphers in [hostsecurity] is not ' + 'supported by this version of Python'), + hint=_('remove the config option or run ' + 'Mercurial with a modern Python ' + 'version (preferred)')) self._ciphers = ciphers @@ -131,6 +135,8 @@ def _hostsettings(ui, hostname): 'verifymode': None, # Defines extra ssl.OP* bitwise options to set. 'ctxoptions': None, + # OpenSSL Cipher List to use (instead of default). + 'ciphers': None, } # Despite its name, PROTOCOL_SSLv23 selects the highest protocol @@ -183,6 +189,10 @@ def _hostsettings(ui, hostname): s['protocol'], s['ctxoptions'] = protocolsettings(protocol) + ciphers = ui.config('hostsecurity', 'ciphers') + ciphers = ui.config('hostsecurity', '%s:ciphers' % hostname, ciphers) + s['ciphers'] = ciphers + # Look for fingerprints in [hostsecurity] section. Value is a list # of : strings. fingerprints = ui.configlist('hostsecurity', '%s:fingerprints' % hostname, @@ -347,6 +357,14 @@ def wrapsocket(sock, keyfile, certfile, # This still works on our fake SSLContext. sslcontext.verify_mode = settings['verifymode'] + if settings['ciphers']: + try: + sslcontext.set_ciphers(settings['ciphers']) + except ssl.SSLError as e: + raise error.Abort(_('could not set ciphers: %s') % e.args[0], + hint=_('change cipher string (%s) in config') % + settings['ciphers']) + if certfile is not None: def password(): f = keyfile or certfile diff --git a/tests/test-https.t b/tests/test-https.t --- a/tests/test-https.t +++ b/tests/test-https.t @@ -326,6 +326,48 @@ Disabling the TLS 1.0 warning works > --config hostsecurity.disabletls10warning=true 5fed3813f7f5 +#if no-sslcontext no-py27+ +Setting ciphers doesn't work in Python 2.6 + $ P="$CERTSDIR" hg --config hostsecurity.ciphers=HIGH -R copy-pull id https://localhost:$HGPORT/ + warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info + abort: setting ciphers in [hostsecurity] is not supported by this version of Python + (remove the config option or run Mercurial with a modern Python version (preferred)) + [255] +#endif + +Setting ciphers works in Python 2.7+ but the error message is different on +legacy ssl. We test legacy once and do more feature checking on modern +configs. + +#if py27+ no-sslcontext + $ P="$CERTSDIR" hg --config hostsecurity.ciphers=invalid -R copy-pull id https://localhost:$HGPORT/ + warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info + abort: *No cipher can be selected. (glob) + [255] + + $ P="$CERTSDIR" hg --config hostsecurity.ciphers=HIGH -R copy-pull id https://localhost:$HGPORT/ + warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info + 5fed3813f7f5 +#endif + +#if sslcontext +Setting ciphers to an invalid value aborts + $ P="$CERTSDIR" hg --config hostsecurity.ciphers=invalid -R copy-pull id https://localhost:$HGPORT/ + abort: could not set ciphers: No cipher can be selected. + (change cipher string (invalid) in config) + [255] + + $ P="$CERTSDIR" hg --config hostsecurity.localhost:ciphers=invalid -R copy-pull id https://localhost:$HGPORT/ + abort: could not set ciphers: No cipher can be selected. + (change cipher string (invalid) in config) + [255] + +Changing the cipher string works + + $ P="$CERTSDIR" hg --config hostsecurity.ciphers=HIGH -R copy-pull id https://localhost:$HGPORT/ + 5fed3813f7f5 +#endif + Fingerprints - works without cacerts (hostkeyfingerprints)