##// 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 ``hostsecurity``
1000 ``hostsecurity``
1001 ----------------
1001 ----------------
1002
1002
1003 Used to specify per-host security settings.
1003 Used to specify global and per-host security settings for connecting to
1004
1004 other machines.
1005 Options in this section have the form ``hostname``:``setting``. This allows
1005
1006 multiple settings to be defined on a per-host basis.
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 The following per-host settings can be defined.
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 This option takes precedence over ``verifycertsfile``.
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 ``verifycertsfile``
1045 ``verifycertsfile``
1030 Path to file a containing a list of PEM encoded certificates used to
1046 Path to file a containing a list of PEM encoded certificates used to
1031 verify the server certificate. Environment variables and ``~user``
1047 verify the server certificate. Environment variables and ``~user``
@@ -1058,6 +1074,13 b' For example::'
1058 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
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 foo.example.com:verifycertsfile = /etc/ssl/trusted-ca-certs.pem
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 ``http_proxy``
1084 ``http_proxy``
1062 --------------
1085 --------------
1063
1086
@@ -29,14 +29,13 b' from . import ('
29 # modern/secure or legacy/insecure. Many operations in this module have
29 # modern/secure or legacy/insecure. Many operations in this module have
30 # separate code paths depending on support in Python.
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:
38 hassni = getattr(ssl, 'HAS_SNI', False)
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
40
39
41 try:
40 try:
42 # ssl.SSLContext was added in 2.7.9 and presence indicates modern
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 # Despite its name, PROTOCOL_SSLv23 selects the highest protocol
136 # Despite its name, PROTOCOL_SSLv23 selects the highest protocol
138 # that both ends support, including TLS protocols. On legacy stacks,
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 # support TLS 1.2.
139 # support TLS 1.2.
141 #
140 #
142 # The PROTOCOL_TLSv* constants select a specific TLS version
141 # The PROTOCOL_TLSv* constants select a specific TLS version
@@ -145,19 +144,26 b' def _hostsettings(ui, hostname):'
145 # disable protocols via SSLContext.options and OP_NO_* constants.
144 # disable protocols via SSLContext.options and OP_NO_* constants.
146 # However, SSLContext.options doesn't work unless we have the
145 # However, SSLContext.options doesn't work unless we have the
147 # full/real SSLContext available to us.
146 # full/real SSLContext available to us.
148 if modernssl:
147
149 s['protocol'] = ssl.PROTOCOL_SSLv23
148 # Allow minimum TLS protocol to be specified in the config.
150 else:
149 def validateprotocol(protocol, key):
151 s['protocol'] = ssl.PROTOCOL_TLSv1
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.
157 key = 'minimumprotocol'
154 # WARNING: ctxoptions doesn't have an effect unless the modern ssl module
158 # Default to TLS 1.0+ as that is what browsers are currently doing.
155 # is available. Be careful when adding flags!
159 protocol = ui.config('hostsecurity', key, 'tls1.0')
156 s['ctxoptions'] = OP_NO_SSLv2 | OP_NO_SSLv3
160 validateprotocol(protocol, key)
157
161
158 # Prevent CRIME.
162 key = '%s:minimumprotocol' % hostname
159 # There is no guarantee this attribute is defined on the module.
163 protocol = ui.config('hostsecurity', key, protocol)
160 s['ctxoptions'] |= getattr(ssl, 'OP_NO_COMPRESSION', 0)
164 validateprotocol(protocol, key)
165
166 s['protocol'], s['ctxoptions'] = protocolsettings(protocol)
161
167
162 # Look for fingerprints in [hostsecurity] section. Value is a list
168 # Look for fingerprints in [hostsecurity] section. Value is a list
163 # of <alg>:<fingerprint> strings.
169 # of <alg>:<fingerprint> strings.
@@ -250,6 +256,46 b' def _hostsettings(ui, hostname):'
250
256
251 return s
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 def wrapsocket(sock, keyfile, certfile, ui, serverhostname=None):
299 def wrapsocket(sock, keyfile, certfile, ui, serverhostname=None):
254 """Add SSL/TLS to a socket.
300 """Add SSL/TLS to a socket.
255
301
@@ -306,7 +352,7 b' def wrapsocket(sock, keyfile, certfile, '
306
352
307 try:
353 try:
308 sslsocket = sslcontext.wrap_socket(sock, server_hostname=serverhostname)
354 sslsocket = sslcontext.wrap_socket(sock, server_hostname=serverhostname)
309 except ssl.SSLError:
355 except ssl.SSLError as e:
310 # If we're doing certificate verification and no CA certs are loaded,
356 # If we're doing certificate verification and no CA certs are loaded,
311 # that is almost certainly the reason why verification failed. Provide
357 # that is almost certainly the reason why verification failed. Provide
312 # a hint to the user.
358 # a hint to the user.
@@ -318,6 +364,13 b' def wrapsocket(sock, keyfile, certfile, '
318 'were loaded; see '
364 'were loaded; see '
319 'https://mercurial-scm.org/wiki/SecureConnections for '
365 'https://mercurial-scm.org/wiki/SecureConnections for '
320 'how to configure Mercurial to avoid this error)\n'))
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 raise
374 raise
322
375
323 # check if wrap_socket failed silently because socket had been
376 # check if wrap_socket failed silently because socket had been
@@ -349,14 +402,28 b' def wrapserversocket(sock, ui, certfile='
349
402
350 Typically ``cafile`` is only defined if ``requireclientcert`` is true.
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 if modernssl:
420 if modernssl:
353 # We /could/ use create_default_context() here since it doesn't load
421 # We /could/ use create_default_context() here since it doesn't load
354 # CAs when configured for client auth.
422 # CAs when configured for client auth. However, it is hard-coded to
355 sslcontext = SSLContext(ssl.PROTOCOL_SSLv23)
423 # use ssl.PROTOCOL_SSLv23 which may not be appropriate here.
356 # SSLv2 and SSLv3 are broken. Ban them outright.
424 sslcontext = SSLContext(protocol)
357 sslcontext.options |= OP_NO_SSLv2 | OP_NO_SSLv3
425 sslcontext.options |= options
358 # Prevent CRIME
426
359 sslcontext.options |= getattr(ssl, 'OP_NO_COMPRESSION', 0)
360 # Improve forward secrecy.
427 # Improve forward secrecy.
361 sslcontext.options |= getattr(ssl, 'OP_SINGLE_DH_USE', 0)
428 sslcontext.options |= getattr(ssl, 'OP_SINGLE_DH_USE', 0)
362 sslcontext.options |= getattr(ssl, 'OP_SINGLE_ECDH_USE', 0)
429 sslcontext.options |= getattr(ssl, 'OP_SINGLE_ECDH_USE', 0)
@@ -345,11 +345,79 b' Fingerprints'
345 $ hg -R copy-pull id https://127.0.0.1:$HGPORT/ --config hostfingerprints.127.0.0.1=ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03
345 $ hg -R copy-pull id https://127.0.0.1:$HGPORT/ --config hostfingerprints.127.0.0.1=ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03
346 5fed3813f7f5
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 $ killdaemons.py hg1.pid
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 Prepare for connecting through proxy
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 $ tinyproxy.py $HGPORT1 localhost >proxy.log </dev/null 2>&1 &
421 $ tinyproxy.py $HGPORT1 localhost >proxy.log </dev/null 2>&1 &
354 $ while [ ! -f proxy.pid ]; do sleep 0; done
422 $ while [ ! -f proxy.pid ]; do sleep 0; done
355 $ cat proxy.pid >> $DAEMON_PIDS
423 $ cat proxy.pid >> $DAEMON_PIDS
General Comments 0
You need to be logged in to leave comments. Login now