##// END OF EJS Templates
sslutil: config option to specify TLS protocol version...
Gregory Szorc -
r29559:7dec5e44 default
parent child Browse files
Show More
@@ -1000,10 +1000,22 b' For example::'
1000 1000 ``hostsecurity``
1001 1001 ----------------
1002 1002
1003 Used to specify per-host security settings.
1004
1005 Options in this section have the form ``hostname``:``setting``. This allows
1006 multiple settings to be defined on a per-host basis.
1003 Used to specify global and per-host security settings for connecting to
1004 other machines.
1005
1006 The following options control default behavior for all hosts.
1007
1008 ``minimumprotocol``
1009 Defines the minimum channel encryption protocol to use.
1010
1011 By default, the highest version of TLS - 1.0 or greater - supported by
1012 both client and server is used.
1013
1014 Allowed values are: ``tls1.0`` (the default), ``tls1.1``, ``tls1.2``.
1015
1016 Options in the ``[hostsecurity]`` section can have the form
1017 ``hostname``:``setting``. This allows multiple settings to be defined on a
1018 per-host basis.
1007 1019
1008 1020 The following per-host settings can be defined.
1009 1021
@@ -1026,6 +1038,10 b' The following per-host settings can be d'
1026 1038
1027 1039 This option takes precedence over ``verifycertsfile``.
1028 1040
1041 ``minimumprotocol``
1042 This behaves like ``minimumprotocol`` as described above except it
1043 only applies to the host on which it is defined.
1044
1029 1045 ``verifycertsfile``
1030 1046 Path to file a containing a list of PEM encoded certificates used to
1031 1047 verify the server certificate. Environment variables and ``~user``
@@ -1058,6 +1074,13 b' For example::'
1058 1074 hg2.example.com:fingerprints = sha1:914f1aff87249c09b6859b88b1906d30756491ca, sha1:fc:e2:8d:d9:51:cd:cb:c1:4d:18:6b:b7:44:8d:49:72:57:e6:cd:33
1059 1075 foo.example.com:verifycertsfile = /etc/ssl/trusted-ca-certs.pem
1060 1076
1077 To change the default minimum protocol version to TLS 1.2 but to allow TLS 1.1
1078 when connecting to ``hg.example.com``::
1079
1080 [hostsecurity]
1081 minimumprotocol = tls1.2
1082 hg.example.com:minimumprotocol = tls1.1
1083
1061 1084 ``http_proxy``
1062 1085 --------------
1063 1086
@@ -29,14 +29,13 b' from . import ('
29 29 # modern/secure or legacy/insecure. Many operations in this module have
30 30 # separate code paths depending on support in Python.
31 31
32 hassni = getattr(ssl, 'HAS_SNI', False)
32 configprotocols = set([
33 'tls1.0',
34 'tls1.1',
35 'tls1.2',
36 ])
33 37
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
38 hassni = getattr(ssl, 'HAS_SNI', False)
40 39
41 40 try:
42 41 # ssl.SSLContext was added in 2.7.9 and presence indicates modern
@@ -136,7 +135,7 b' def _hostsettings(ui, hostname):'
136 135
137 136 # Despite its name, PROTOCOL_SSLv23 selects the highest protocol
138 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 139 # support TLS 1.2.
141 140 #
142 141 # The PROTOCOL_TLSv* constants select a specific TLS version
@@ -145,19 +144,26 b' def _hostsettings(ui, hostname):'
145 144 # disable protocols via SSLContext.options and OP_NO_* constants.
146 145 # However, SSLContext.options doesn't work unless we have the
147 146 # full/real SSLContext available to us.
148 if modernssl:
149 s['protocol'] = ssl.PROTOCOL_SSLv23
150 else:
151 s['protocol'] = ssl.PROTOCOL_TLSv1
147
148 # Allow minimum TLS protocol to be specified in the config.
149 def validateprotocol(protocol, key):
150 if protocol not in configprotocols:
151 raise error.Abort(
152 _('unsupported protocol from hostsecurity.%s: %s') %
153 (key, protocol),
154 hint=_('valid protocols: %s') %
155 ' '.join(sorted(configprotocols)))
152 156
153 # SSLv2 and SSLv3 are broken. We ban them outright.
154 # WARNING: ctxoptions doesn't have an effect unless the modern ssl module
155 # is available. Be careful when adding flags!
156 s['ctxoptions'] = OP_NO_SSLv2 | OP_NO_SSLv3
157 key = 'minimumprotocol'
158 # Default to TLS 1.0+ as that is what browsers are currently doing.
159 protocol = ui.config('hostsecurity', key, 'tls1.0')
160 validateprotocol(protocol, key)
157 161
158 # Prevent CRIME.
159 # There is no guarantee this attribute is defined on the module.
160 s['ctxoptions'] |= getattr(ssl, 'OP_NO_COMPRESSION', 0)
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 168 # Look for fingerprints in [hostsecurity] section. Value is a list
163 169 # of <alg>:<fingerprint> strings.
@@ -250,6 +256,46 b' def _hostsettings(ui, hostname):'
250 256
251 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 299 def wrapsocket(sock, keyfile, certfile, ui, serverhostname=None):
254 300 """Add SSL/TLS to a socket.
255 301
@@ -306,7 +352,7 b' def wrapsocket(sock, keyfile, certfile, '
306 352
307 353 try:
308 354 sslsocket = sslcontext.wrap_socket(sock, server_hostname=serverhostname)
309 except ssl.SSLError:
355 except ssl.SSLError as e:
310 356 # If we're doing certificate verification and no CA certs are loaded,
311 357 # that is almost certainly the reason why verification failed. Provide
312 358 # a hint to the user.
@@ -318,6 +364,13 b' def wrapsocket(sock, keyfile, certfile, '
318 364 'were loaded; see '
319 365 'https://mercurial-scm.org/wiki/SecureConnections for '
320 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 374 raise
322 375
323 376 # check if wrap_socket failed silently because socket had been
@@ -349,14 +402,28 b' def wrapserversocket(sock, ui, certfile='
349 402
350 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 420 if modernssl:
353 421 # We /could/ use create_default_context() here since it doesn't load
354 # CAs when configured for client auth.
355 sslcontext = SSLContext(ssl.PROTOCOL_SSLv23)
356 # SSLv2 and SSLv3 are broken. Ban them outright.
357 sslcontext.options |= OP_NO_SSLv2 | OP_NO_SSLv3
358 # Prevent CRIME
359 sslcontext.options |= getattr(ssl, 'OP_NO_COMPRESSION', 0)
422 # CAs when configured for client auth. However, it is hard-coded to
423 # use ssl.PROTOCOL_SSLv23 which may not be appropriate here.
424 sslcontext = SSLContext(protocol)
425 sslcontext.options |= options
426
360 427 # Improve forward secrecy.
361 428 sslcontext.options |= getattr(ssl, 'OP_SINGLE_DH_USE', 0)
362 429 sslcontext.options |= getattr(ssl, 'OP_SINGLE_ECDH_USE', 0)
@@ -345,11 +345,79 b' Fingerprints'
345 345 $ hg -R copy-pull id https://127.0.0.1:$HGPORT/ --config hostfingerprints.127.0.0.1=ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03
346 346 5fed3813f7f5
347 347
348 HGPORT1 is reused below for tinyproxy tests. Kill that server.
348 Ports used by next test. Kill servers.
349
350 $ killdaemons.py hg0.pid
349 351 $ killdaemons.py hg1.pid
352 $ killdaemons.py hg2.pid
353
354 #if sslcontext
355 Start servers running supported TLS versions
356
357 $ cd test
358 $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV \
359 > --config devel.serverexactprotocol=tls1.0
360 $ cat ../hg0.pid >> $DAEMON_PIDS
361 $ hg serve -p $HGPORT1 -d --pid-file=../hg1.pid --certificate=$PRIV \
362 > --config devel.serverexactprotocol=tls1.1
363 $ cat ../hg1.pid >> $DAEMON_PIDS
364 $ hg serve -p $HGPORT2 -d --pid-file=../hg2.pid --certificate=$PRIV \
365 > --config devel.serverexactprotocol=tls1.2
366 $ cat ../hg2.pid >> $DAEMON_PIDS
367 $ cd ..
368
369 Clients talking same TLS versions work
370
371 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.0 id https://localhost:$HGPORT/
372 5fed3813f7f5
373 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.1 id https://localhost:$HGPORT1/
374 5fed3813f7f5
375 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.2 id https://localhost:$HGPORT2/
376 5fed3813f7f5
377
378 Clients requiring newer TLS version than what server supports fail
379
380 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.1 id https://localhost:$HGPORT/
381 (could not negotiate a common protocol; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error)
382 abort: error: *unsupported protocol* (glob)
383 [255]
384 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.2 id https://localhost:$HGPORT/
385 (could not negotiate a common protocol; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error)
386 abort: error: *unsupported protocol* (glob)
387 [255]
388 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.2 id https://localhost:$HGPORT1/
389 (could not negotiate a common protocol; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error)
390 abort: error: *unsupported protocol* (glob)
391 [255]
392
393 The per-host config option overrides the default
394
395 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
396 > --config hostsecurity.minimumprotocol=tls1.2 \
397 > --config hostsecurity.localhost:minimumprotocol=tls1.0
398 5fed3813f7f5
399
400 The per-host config option by itself works
401
402 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
403 > --config hostsecurity.localhost:minimumprotocol=tls1.2
404 (could not negotiate a common protocol; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error)
405 abort: error: *unsupported protocol* (glob)
406 [255]
407
408 $ killdaemons.py hg0.pid
409 $ killdaemons.py hg1.pid
410 $ killdaemons.py hg2.pid
411 #endif
350 412
351 413 Prepare for connecting through proxy
352 414
415 $ hg serve -R test -p $HGPORT -d --pid-file=hg0.pid --certificate=$PRIV
416 $ cat hg0.pid >> $DAEMON_PIDS
417 $ hg serve -R test -p $HGPORT2 -d --pid-file=hg2.pid --certificate=server-expired.pem
418 $ cat hg2.pid >> $DAEMON_PIDS
419 tinyproxy.py doesn't fully detach, so killing it may result in extra output
420 from the shell. So don't kill it.
353 421 $ tinyproxy.py $HGPORT1 localhost >proxy.log </dev/null 2>&1 &
354 422 $ while [ ! -f proxy.pid ]; do sleep 0; done
355 423 $ cat proxy.pid >> $DAEMON_PIDS
General Comments 0
You need to be logged in to leave comments. Login now