@@ -1000,30 +1000,46 @@ For example::
``hostsecurity``
----------------
Used to specify global and per-host security settings for connecting to
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.
By default, the highest version of TLS - 1.0 or greater - supported by
both client and server is used.
Allowed values are: ``tls1.0`` (the default), ``tls1.1``, ``tls1.2``.
Options in the ``[hostsecurity]`` section can have the form
``hostname``:``setting``. This allows multiple settings to be defined on a
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.
``sha256:c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2``.
The following algorithms/prefixes are supported: ``sha1``, ``sha256``,
``sha512``.
@@ -79,17 +79,21 @@ except AttributeError:
raise error.Abort(_('capath not supported'))
if cadata:
raise error.Abort(_('cadata not supported'))
self._cacerts = cafile
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
def wrap_socket(self, socket, server_hostname=None, server_side=False):
# server_hostname is unique to SSLContext.wrap_socket and is used
# for SNI in that context. So there's nothing for us to do with it
# in this legacy code since we don't support SNI.
@@ -126,16 +130,18 @@ def _hostsettings(ui, hostname):
# Whether the legacy [hostfingerprints] section has data for this host.
'legacyfingerprint': False,
# PROTOCOL_* constant to use for SSLContext.__init__.
'protocol': None,
# ssl.CERT_* constant used by SSLContext.verify_mode.
'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
# that both ends support, including TLS protocols. On legacy stacks,
# the highest it likely goes is TLS 1.0. On modern stacks, it can
# support TLS 1.2.
#
# The PROTOCOL_TLSv* constants select a specific TLS version
@@ -160,16 +166,20 @@ def _hostsettings(ui, hostname):
validateprotocol(protocol, key)
key = '%s:minimumprotocol' % hostname
protocol = ui.config('hostsecurity', key, protocol)
validateprotocol(protocol, key)
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 <alg>:<fingerprint> strings.
fingerprints = ui.configlist('hostsecurity', '%s:fingerprints' % hostname,
[])
for fingerprint in fingerprints:
if not (fingerprint.startswith(('sha1:', 'sha256:', 'sha512:'))):
raise error.Abort(_('invalid fingerprint for %s: %s') % (
hostname, fingerprint),
@@ -324,16 +334,24 @@ def wrapsocket(sock, keyfile, certfile,
sslcontext = SSLContext(settings['protocol'])
# This is a no-op unless using modern ssl.
sslcontext.options |= settings['ctxoptions']
# 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
return ui.getpass(_('passphrase for %s: ') % f, '')
sslcontext.load_cert_chain(certfile, keyfile, password)
if settings['cafile'] is not None:
try:
@@ -290,16 +290,42 @@ Test server cert which no longer is vali
$ hg serve -R test -p $HGPORT2 -d --pid-file=hg2.pid --certificate=server-expired.pem
$ cat hg2.pid >> $DAEMON_PIDS
$ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-expired.pem" \
> https://localhost:$HGPORT2/
pulling from https://localhost:$HGPORT2/
abort: error: *certificate verify failed* (glob)
[255]
+#if no-sslcontext
+Setting ciphers requires Python 2.7 (any version - it doesn't require SSLContext)
+ $ P="$CERTSDIR" hg --config hostsecurity.ciphers=HIGH -R copy-pull id https://localhost:$HGPORT/
+ 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
+
+#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)
$ hg -R copy-pull id https://localhost:$HGPORT/ --insecure --config hostfingerprints.localhost=ec:d8:7c:d6:b3:86:d0:4f:c1:b8:b4:1c:9d:8f:5e:16:8e:ef:1c:03
5fed3813f7f5
- works without cacerts (hostsecurity)
$ hg -R copy-pull id https://localhost:$HGPORT/ --config hostsecurity.localhost:fingerprints=sha1:ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03