Mercurial > hg
comparison mercurial/sslutil.py @ 29577:9654ef41f7cc
sslutil: support defining cipher list
Python 2.7 supports specifying a custom cipher list to TLS sockets.
Advanced users may wish to specify a custom cipher list to increase
security. Or in some cases they may wish to prefer weaker ciphers
in order to increase performance (e.g. when doing stream clones
of very large repositories).
This patch introduces a [hostsecurity] config option for defining
the cipher list. The help documentation states that it is for
advanced users only.
Honestly, I'm a bit on the fence about providing this because
it is a footgun and can be used to decrease security. However,
there are legitimate use cases for it, so I think support should
be provided.
author | Gregory Szorc <gregory.szorc@gmail.com> |
---|---|
date | Sun, 17 Jul 2016 10:59:32 -0700 |
parents | 1a782fabf80d |
children | 4a4b8d3b4e43 |
comparison
equal
deleted
inserted
replaced
29576:d5067913f97b | 29577:9654ef41f7cc |
---|---|
82 | 82 |
83 self._cacerts = cafile | 83 self._cacerts = cafile |
84 | 84 |
85 def set_ciphers(self, ciphers): | 85 def set_ciphers(self, ciphers): |
86 if not self._supportsciphers: | 86 if not self._supportsciphers: |
87 raise error.Abort(_('setting ciphers not supported')) | 87 raise error.Abort(_('setting ciphers in [hostsecurity] is not ' |
88 'supported by this version of Python'), | |
89 hint=_('remove the config option or run ' | |
90 'Mercurial with a modern Python ' | |
91 'version (preferred)')) | |
88 | 92 |
89 self._ciphers = ciphers | 93 self._ciphers = ciphers |
90 | 94 |
91 def wrap_socket(self, socket, server_hostname=None, server_side=False): | 95 def wrap_socket(self, socket, server_hostname=None, server_side=False): |
92 # server_hostname is unique to SSLContext.wrap_socket and is used | 96 # server_hostname is unique to SSLContext.wrap_socket and is used |
129 'protocol': None, | 133 'protocol': None, |
130 # ssl.CERT_* constant used by SSLContext.verify_mode. | 134 # ssl.CERT_* constant used by SSLContext.verify_mode. |
131 'verifymode': None, | 135 'verifymode': None, |
132 # Defines extra ssl.OP* bitwise options to set. | 136 # Defines extra ssl.OP* bitwise options to set. |
133 'ctxoptions': None, | 137 'ctxoptions': None, |
138 # OpenSSL Cipher List to use (instead of default). | |
139 'ciphers': None, | |
134 } | 140 } |
135 | 141 |
136 # Despite its name, PROTOCOL_SSLv23 selects the highest protocol | 142 # Despite its name, PROTOCOL_SSLv23 selects the highest protocol |
137 # that both ends support, including TLS protocols. On legacy stacks, | 143 # that both ends support, including TLS protocols. On legacy stacks, |
138 # the highest it likely goes is TLS 1.0. On modern stacks, it can | 144 # the highest it likely goes is TLS 1.0. On modern stacks, it can |
181 protocol = ui.config('hostsecurity', key, protocol) | 187 protocol = ui.config('hostsecurity', key, protocol) |
182 validateprotocol(protocol, key) | 188 validateprotocol(protocol, key) |
183 | 189 |
184 s['protocol'], s['ctxoptions'] = protocolsettings(protocol) | 190 s['protocol'], s['ctxoptions'] = protocolsettings(protocol) |
185 | 191 |
192 ciphers = ui.config('hostsecurity', 'ciphers') | |
193 ciphers = ui.config('hostsecurity', '%s:ciphers' % hostname, ciphers) | |
194 s['ciphers'] = ciphers | |
195 | |
186 # Look for fingerprints in [hostsecurity] section. Value is a list | 196 # Look for fingerprints in [hostsecurity] section. Value is a list |
187 # of <alg>:<fingerprint> strings. | 197 # of <alg>:<fingerprint> strings. |
188 fingerprints = ui.configlist('hostsecurity', '%s:fingerprints' % hostname, | 198 fingerprints = ui.configlist('hostsecurity', '%s:fingerprints' % hostname, |
189 []) | 199 []) |
190 for fingerprint in fingerprints: | 200 for fingerprint in fingerprints: |
344 # This is a no-op unless using modern ssl. | 354 # This is a no-op unless using modern ssl. |
345 sslcontext.options |= settings['ctxoptions'] | 355 sslcontext.options |= settings['ctxoptions'] |
346 | 356 |
347 # This still works on our fake SSLContext. | 357 # This still works on our fake SSLContext. |
348 sslcontext.verify_mode = settings['verifymode'] | 358 sslcontext.verify_mode = settings['verifymode'] |
359 | |
360 if settings['ciphers']: | |
361 try: | |
362 sslcontext.set_ciphers(settings['ciphers']) | |
363 except ssl.SSLError as e: | |
364 raise error.Abort(_('could not set ciphers: %s') % e.args[0], | |
365 hint=_('change cipher string (%s) in config') % | |
366 settings['ciphers']) | |
349 | 367 |
350 if certfile is not None: | 368 if certfile is not None: |
351 def password(): | 369 def password(): |
352 f = keyfile or certfile | 370 f = keyfile or certfile |
353 return ui.getpass(_('passphrase for %s: ') % f, '') | 371 return ui.getpass(_('passphrase for %s: ') % f, '') |