##// END OF EJS Templates
sslutil: improve messaging around unsupported protocols (issue5303)...
Gregory Szorc -
r29619:53e80179 stable
parent child Browse files
Show More
@@ -1,788 +1,834 b''
1 # sslutil.py - SSL handling for mercurial
1 # sslutil.py - SSL handling for mercurial
2 #
2 #
3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 from __future__ import absolute_import
10 from __future__ import absolute_import
11
11
12 import hashlib
12 import hashlib
13 import os
13 import os
14 import re
14 import re
15 import ssl
15 import ssl
16 import sys
16 import sys
17
17
18 from .i18n import _
18 from .i18n import _
19 from . import (
19 from . import (
20 error,
20 error,
21 util,
21 util,
22 )
22 )
23
23
24 # Python 2.7.9+ overhauled the built-in SSL/TLS features of Python. It added
24 # Python 2.7.9+ overhauled the built-in SSL/TLS features of Python. It added
25 # support for TLS 1.1, TLS 1.2, SNI, system CA stores, etc. These features are
25 # support for TLS 1.1, TLS 1.2, SNI, system CA stores, etc. These features are
26 # all exposed via the "ssl" module.
26 # all exposed via the "ssl" module.
27 #
27 #
28 # Depending on the version of Python being used, SSL/TLS support is either
28 # Depending on the version of Python being used, SSL/TLS support is either
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 configprotocols = set([
32 configprotocols = set([
33 'tls1.0',
33 'tls1.0',
34 'tls1.1',
34 'tls1.1',
35 'tls1.2',
35 'tls1.2',
36 ])
36 ])
37
37
38 hassni = getattr(ssl, 'HAS_SNI', False)
38 hassni = getattr(ssl, 'HAS_SNI', False)
39
39
40 # TLS 1.1 and 1.2 may not be supported if the OpenSSL Python is compiled
40 # TLS 1.1 and 1.2 may not be supported if the OpenSSL Python is compiled
41 # against doesn't support them.
41 # against doesn't support them.
42 supportedprotocols = set(['tls1.0'])
42 supportedprotocols = set(['tls1.0'])
43 if util.safehasattr(ssl, 'PROTOCOL_TLSv1_1'):
43 if util.safehasattr(ssl, 'PROTOCOL_TLSv1_1'):
44 supportedprotocols.add('tls1.1')
44 supportedprotocols.add('tls1.1')
45 if util.safehasattr(ssl, 'PROTOCOL_TLSv1_2'):
45 if util.safehasattr(ssl, 'PROTOCOL_TLSv1_2'):
46 supportedprotocols.add('tls1.2')
46 supportedprotocols.add('tls1.2')
47
47
48 try:
48 try:
49 # ssl.SSLContext was added in 2.7.9 and presence indicates modern
49 # ssl.SSLContext was added in 2.7.9 and presence indicates modern
50 # SSL/TLS features are available.
50 # SSL/TLS features are available.
51 SSLContext = ssl.SSLContext
51 SSLContext = ssl.SSLContext
52 modernssl = True
52 modernssl = True
53 _canloaddefaultcerts = util.safehasattr(SSLContext, 'load_default_certs')
53 _canloaddefaultcerts = util.safehasattr(SSLContext, 'load_default_certs')
54 except AttributeError:
54 except AttributeError:
55 modernssl = False
55 modernssl = False
56 _canloaddefaultcerts = False
56 _canloaddefaultcerts = False
57
57
58 # We implement SSLContext using the interface from the standard library.
58 # We implement SSLContext using the interface from the standard library.
59 class SSLContext(object):
59 class SSLContext(object):
60 # ssl.wrap_socket gained the "ciphers" named argument in 2.7.
60 # ssl.wrap_socket gained the "ciphers" named argument in 2.7.
61 _supportsciphers = sys.version_info >= (2, 7)
61 _supportsciphers = sys.version_info >= (2, 7)
62
62
63 def __init__(self, protocol):
63 def __init__(self, protocol):
64 # From the public interface of SSLContext
64 # From the public interface of SSLContext
65 self.protocol = protocol
65 self.protocol = protocol
66 self.check_hostname = False
66 self.check_hostname = False
67 self.options = 0
67 self.options = 0
68 self.verify_mode = ssl.CERT_NONE
68 self.verify_mode = ssl.CERT_NONE
69
69
70 # Used by our implementation.
70 # Used by our implementation.
71 self._certfile = None
71 self._certfile = None
72 self._keyfile = None
72 self._keyfile = None
73 self._certpassword = None
73 self._certpassword = None
74 self._cacerts = None
74 self._cacerts = None
75 self._ciphers = None
75 self._ciphers = None
76
76
77 def load_cert_chain(self, certfile, keyfile=None, password=None):
77 def load_cert_chain(self, certfile, keyfile=None, password=None):
78 self._certfile = certfile
78 self._certfile = certfile
79 self._keyfile = keyfile
79 self._keyfile = keyfile
80 self._certpassword = password
80 self._certpassword = password
81
81
82 def load_default_certs(self, purpose=None):
82 def load_default_certs(self, purpose=None):
83 pass
83 pass
84
84
85 def load_verify_locations(self, cafile=None, capath=None, cadata=None):
85 def load_verify_locations(self, cafile=None, capath=None, cadata=None):
86 if capath:
86 if capath:
87 raise error.Abort(_('capath not supported'))
87 raise error.Abort(_('capath not supported'))
88 if cadata:
88 if cadata:
89 raise error.Abort(_('cadata not supported'))
89 raise error.Abort(_('cadata not supported'))
90
90
91 self._cacerts = cafile
91 self._cacerts = cafile
92
92
93 def set_ciphers(self, ciphers):
93 def set_ciphers(self, ciphers):
94 if not self._supportsciphers:
94 if not self._supportsciphers:
95 raise error.Abort(_('setting ciphers in [hostsecurity] is not '
95 raise error.Abort(_('setting ciphers in [hostsecurity] is not '
96 'supported by this version of Python'),
96 'supported by this version of Python'),
97 hint=_('remove the config option or run '
97 hint=_('remove the config option or run '
98 'Mercurial with a modern Python '
98 'Mercurial with a modern Python '
99 'version (preferred)'))
99 'version (preferred)'))
100
100
101 self._ciphers = ciphers
101 self._ciphers = ciphers
102
102
103 def wrap_socket(self, socket, server_hostname=None, server_side=False):
103 def wrap_socket(self, socket, server_hostname=None, server_side=False):
104 # server_hostname is unique to SSLContext.wrap_socket and is used
104 # server_hostname is unique to SSLContext.wrap_socket and is used
105 # for SNI in that context. So there's nothing for us to do with it
105 # for SNI in that context. So there's nothing for us to do with it
106 # in this legacy code since we don't support SNI.
106 # in this legacy code since we don't support SNI.
107
107
108 args = {
108 args = {
109 'keyfile': self._keyfile,
109 'keyfile': self._keyfile,
110 'certfile': self._certfile,
110 'certfile': self._certfile,
111 'server_side': server_side,
111 'server_side': server_side,
112 'cert_reqs': self.verify_mode,
112 'cert_reqs': self.verify_mode,
113 'ssl_version': self.protocol,
113 'ssl_version': self.protocol,
114 'ca_certs': self._cacerts,
114 'ca_certs': self._cacerts,
115 }
115 }
116
116
117 if self._supportsciphers:
117 if self._supportsciphers:
118 args['ciphers'] = self._ciphers
118 args['ciphers'] = self._ciphers
119
119
120 return ssl.wrap_socket(socket, **args)
120 return ssl.wrap_socket(socket, **args)
121
121
122 def _hostsettings(ui, hostname):
122 def _hostsettings(ui, hostname):
123 """Obtain security settings for a hostname.
123 """Obtain security settings for a hostname.
124
124
125 Returns a dict of settings relevant to that hostname.
125 Returns a dict of settings relevant to that hostname.
126 """
126 """
127 s = {
127 s = {
128 # Whether we should attempt to load default/available CA certs
128 # Whether we should attempt to load default/available CA certs
129 # if an explicit ``cafile`` is not defined.
129 # if an explicit ``cafile`` is not defined.
130 'allowloaddefaultcerts': True,
130 'allowloaddefaultcerts': True,
131 # List of 2-tuple of (hash algorithm, hash).
131 # List of 2-tuple of (hash algorithm, hash).
132 'certfingerprints': [],
132 'certfingerprints': [],
133 # Path to file containing concatenated CA certs. Used by
133 # Path to file containing concatenated CA certs. Used by
134 # SSLContext.load_verify_locations().
134 # SSLContext.load_verify_locations().
135 'cafile': None,
135 'cafile': None,
136 # Whether certificate verification should be disabled.
136 # Whether certificate verification should be disabled.
137 'disablecertverification': False,
137 'disablecertverification': False,
138 # Whether the legacy [hostfingerprints] section has data for this host.
138 # Whether the legacy [hostfingerprints] section has data for this host.
139 'legacyfingerprint': False,
139 'legacyfingerprint': False,
140 # PROTOCOL_* constant to use for SSLContext.__init__.
140 # PROTOCOL_* constant to use for SSLContext.__init__.
141 'protocol': None,
141 'protocol': None,
142 # String representation of minimum protocol to be used for UI
142 # String representation of minimum protocol to be used for UI
143 # presentation.
143 # presentation.
144 'protocolui': None,
144 'protocolui': None,
145 # ssl.CERT_* constant used by SSLContext.verify_mode.
145 # ssl.CERT_* constant used by SSLContext.verify_mode.
146 'verifymode': None,
146 'verifymode': None,
147 # Defines extra ssl.OP* bitwise options to set.
147 # Defines extra ssl.OP* bitwise options to set.
148 'ctxoptions': None,
148 'ctxoptions': None,
149 # OpenSSL Cipher List to use (instead of default).
149 # OpenSSL Cipher List to use (instead of default).
150 'ciphers': None,
150 'ciphers': None,
151 }
151 }
152
152
153 # Allow minimum TLS protocol to be specified in the config.
153 # Allow minimum TLS protocol to be specified in the config.
154 def validateprotocol(protocol, key):
154 def validateprotocol(protocol, key):
155 if protocol not in configprotocols:
155 if protocol not in configprotocols:
156 raise error.Abort(
156 raise error.Abort(
157 _('unsupported protocol from hostsecurity.%s: %s') %
157 _('unsupported protocol from hostsecurity.%s: %s') %
158 (key, protocol),
158 (key, protocol),
159 hint=_('valid protocols: %s') %
159 hint=_('valid protocols: %s') %
160 ' '.join(sorted(configprotocols)))
160 ' '.join(sorted(configprotocols)))
161
161
162 # We default to TLS 1.1+ where we can because TLS 1.0 has known
162 # We default to TLS 1.1+ where we can because TLS 1.0 has known
163 # vulnerabilities (like BEAST and POODLE). We allow users to downgrade to
163 # vulnerabilities (like BEAST and POODLE). We allow users to downgrade to
164 # TLS 1.0+ via config options in case a legacy server is encountered.
164 # TLS 1.0+ via config options in case a legacy server is encountered.
165 if 'tls1.1' in supportedprotocols:
165 if 'tls1.1' in supportedprotocols:
166 defaultprotocol = 'tls1.1'
166 defaultprotocol = 'tls1.1'
167 else:
167 else:
168 # Let people know they are borderline secure.
168 # Let people know they are borderline secure.
169 # We don't document this config option because we want people to see
169 # We don't document this config option because we want people to see
170 # the bold warnings on the web site.
170 # the bold warnings on the web site.
171 # internal config: hostsecurity.disabletls10warning
171 # internal config: hostsecurity.disabletls10warning
172 if not ui.configbool('hostsecurity', 'disabletls10warning'):
172 if not ui.configbool('hostsecurity', 'disabletls10warning'):
173 ui.warn(_('warning: connecting to %s using legacy security '
173 ui.warn(_('warning: connecting to %s using legacy security '
174 'technology (TLS 1.0); see '
174 'technology (TLS 1.0); see '
175 'https://mercurial-scm.org/wiki/SecureConnections for '
175 'https://mercurial-scm.org/wiki/SecureConnections for '
176 'more info\n') % hostname)
176 'more info\n') % hostname)
177 defaultprotocol = 'tls1.0'
177 defaultprotocol = 'tls1.0'
178
178
179 key = 'minimumprotocol'
179 key = 'minimumprotocol'
180 protocol = ui.config('hostsecurity', key, defaultprotocol)
180 protocol = ui.config('hostsecurity', key, defaultprotocol)
181 validateprotocol(protocol, key)
181 validateprotocol(protocol, key)
182
182
183 key = '%s:minimumprotocol' % hostname
183 key = '%s:minimumprotocol' % hostname
184 protocol = ui.config('hostsecurity', key, protocol)
184 protocol = ui.config('hostsecurity', key, protocol)
185 validateprotocol(protocol, key)
185 validateprotocol(protocol, key)
186
186
187 # If --insecure is used, we allow the use of TLS 1.0 despite config options.
187 # If --insecure is used, we allow the use of TLS 1.0 despite config options.
188 # We always print a "connection security to %s is disabled..." message when
188 # We always print a "connection security to %s is disabled..." message when
189 # --insecure is used. So no need to print anything more here.
189 # --insecure is used. So no need to print anything more here.
190 if ui.insecureconnections:
190 if ui.insecureconnections:
191 protocol = 'tls1.0'
191 protocol = 'tls1.0'
192
192
193 s['protocol'], s['ctxoptions'], s['protocolui'] = protocolsettings(protocol)
193 s['protocol'], s['ctxoptions'], s['protocolui'] = protocolsettings(protocol)
194
194
195 ciphers = ui.config('hostsecurity', 'ciphers')
195 ciphers = ui.config('hostsecurity', 'ciphers')
196 ciphers = ui.config('hostsecurity', '%s:ciphers' % hostname, ciphers)
196 ciphers = ui.config('hostsecurity', '%s:ciphers' % hostname, ciphers)
197 s['ciphers'] = ciphers
197 s['ciphers'] = ciphers
198
198
199 # Look for fingerprints in [hostsecurity] section. Value is a list
199 # Look for fingerprints in [hostsecurity] section. Value is a list
200 # of <alg>:<fingerprint> strings.
200 # of <alg>:<fingerprint> strings.
201 fingerprints = ui.configlist('hostsecurity', '%s:fingerprints' % hostname,
201 fingerprints = ui.configlist('hostsecurity', '%s:fingerprints' % hostname,
202 [])
202 [])
203 for fingerprint in fingerprints:
203 for fingerprint in fingerprints:
204 if not (fingerprint.startswith(('sha1:', 'sha256:', 'sha512:'))):
204 if not (fingerprint.startswith(('sha1:', 'sha256:', 'sha512:'))):
205 raise error.Abort(_('invalid fingerprint for %s: %s') % (
205 raise error.Abort(_('invalid fingerprint for %s: %s') % (
206 hostname, fingerprint),
206 hostname, fingerprint),
207 hint=_('must begin with "sha1:", "sha256:", '
207 hint=_('must begin with "sha1:", "sha256:", '
208 'or "sha512:"'))
208 'or "sha512:"'))
209
209
210 alg, fingerprint = fingerprint.split(':', 1)
210 alg, fingerprint = fingerprint.split(':', 1)
211 fingerprint = fingerprint.replace(':', '').lower()
211 fingerprint = fingerprint.replace(':', '').lower()
212 s['certfingerprints'].append((alg, fingerprint))
212 s['certfingerprints'].append((alg, fingerprint))
213
213
214 # Fingerprints from [hostfingerprints] are always SHA-1.
214 # Fingerprints from [hostfingerprints] are always SHA-1.
215 for fingerprint in ui.configlist('hostfingerprints', hostname, []):
215 for fingerprint in ui.configlist('hostfingerprints', hostname, []):
216 fingerprint = fingerprint.replace(':', '').lower()
216 fingerprint = fingerprint.replace(':', '').lower()
217 s['certfingerprints'].append(('sha1', fingerprint))
217 s['certfingerprints'].append(('sha1', fingerprint))
218 s['legacyfingerprint'] = True
218 s['legacyfingerprint'] = True
219
219
220 # If a host cert fingerprint is defined, it is the only thing that
220 # If a host cert fingerprint is defined, it is the only thing that
221 # matters. No need to validate CA certs.
221 # matters. No need to validate CA certs.
222 if s['certfingerprints']:
222 if s['certfingerprints']:
223 s['verifymode'] = ssl.CERT_NONE
223 s['verifymode'] = ssl.CERT_NONE
224 s['allowloaddefaultcerts'] = False
224 s['allowloaddefaultcerts'] = False
225
225
226 # If --insecure is used, don't take CAs into consideration.
226 # If --insecure is used, don't take CAs into consideration.
227 elif ui.insecureconnections:
227 elif ui.insecureconnections:
228 s['disablecertverification'] = True
228 s['disablecertverification'] = True
229 s['verifymode'] = ssl.CERT_NONE
229 s['verifymode'] = ssl.CERT_NONE
230 s['allowloaddefaultcerts'] = False
230 s['allowloaddefaultcerts'] = False
231
231
232 if ui.configbool('devel', 'disableloaddefaultcerts'):
232 if ui.configbool('devel', 'disableloaddefaultcerts'):
233 s['allowloaddefaultcerts'] = False
233 s['allowloaddefaultcerts'] = False
234
234
235 # If both fingerprints and a per-host ca file are specified, issue a warning
235 # If both fingerprints and a per-host ca file are specified, issue a warning
236 # because users should not be surprised about what security is or isn't
236 # because users should not be surprised about what security is or isn't
237 # being performed.
237 # being performed.
238 cafile = ui.config('hostsecurity', '%s:verifycertsfile' % hostname)
238 cafile = ui.config('hostsecurity', '%s:verifycertsfile' % hostname)
239 if s['certfingerprints'] and cafile:
239 if s['certfingerprints'] and cafile:
240 ui.warn(_('(hostsecurity.%s:verifycertsfile ignored when host '
240 ui.warn(_('(hostsecurity.%s:verifycertsfile ignored when host '
241 'fingerprints defined; using host fingerprints for '
241 'fingerprints defined; using host fingerprints for '
242 'verification)\n') % hostname)
242 'verification)\n') % hostname)
243
243
244 # Try to hook up CA certificate validation unless something above
244 # Try to hook up CA certificate validation unless something above
245 # makes it not necessary.
245 # makes it not necessary.
246 if s['verifymode'] is None:
246 if s['verifymode'] is None:
247 # Look at per-host ca file first.
247 # Look at per-host ca file first.
248 if cafile:
248 if cafile:
249 cafile = util.expandpath(cafile)
249 cafile = util.expandpath(cafile)
250 if not os.path.exists(cafile):
250 if not os.path.exists(cafile):
251 raise error.Abort(_('path specified by %s does not exist: %s') %
251 raise error.Abort(_('path specified by %s does not exist: %s') %
252 ('hostsecurity.%s:verifycertsfile' % hostname,
252 ('hostsecurity.%s:verifycertsfile' % hostname,
253 cafile))
253 cafile))
254 s['cafile'] = cafile
254 s['cafile'] = cafile
255 else:
255 else:
256 # Find global certificates file in config.
256 # Find global certificates file in config.
257 cafile = ui.config('web', 'cacerts')
257 cafile = ui.config('web', 'cacerts')
258
258
259 if cafile:
259 if cafile:
260 cafile = util.expandpath(cafile)
260 cafile = util.expandpath(cafile)
261 if not os.path.exists(cafile):
261 if not os.path.exists(cafile):
262 raise error.Abort(_('could not find web.cacerts: %s') %
262 raise error.Abort(_('could not find web.cacerts: %s') %
263 cafile)
263 cafile)
264 elif s['allowloaddefaultcerts']:
264 elif s['allowloaddefaultcerts']:
265 # CAs not defined in config. Try to find system bundles.
265 # CAs not defined in config. Try to find system bundles.
266 cafile = _defaultcacerts(ui)
266 cafile = _defaultcacerts(ui)
267 if cafile:
267 if cafile:
268 ui.debug('using %s for CA file\n' % cafile)
268 ui.debug('using %s for CA file\n' % cafile)
269
269
270 s['cafile'] = cafile
270 s['cafile'] = cafile
271
271
272 # Require certificate validation if CA certs are being loaded and
272 # Require certificate validation if CA certs are being loaded and
273 # verification hasn't been disabled above.
273 # verification hasn't been disabled above.
274 if cafile or (_canloaddefaultcerts and s['allowloaddefaultcerts']):
274 if cafile or (_canloaddefaultcerts and s['allowloaddefaultcerts']):
275 s['verifymode'] = ssl.CERT_REQUIRED
275 s['verifymode'] = ssl.CERT_REQUIRED
276 else:
276 else:
277 # At this point we don't have a fingerprint, aren't being
277 # At this point we don't have a fingerprint, aren't being
278 # explicitly insecure, and can't load CA certs. Connecting
278 # explicitly insecure, and can't load CA certs. Connecting
279 # is insecure. We allow the connection and abort during
279 # is insecure. We allow the connection and abort during
280 # validation (once we have the fingerprint to print to the
280 # validation (once we have the fingerprint to print to the
281 # user).
281 # user).
282 s['verifymode'] = ssl.CERT_NONE
282 s['verifymode'] = ssl.CERT_NONE
283
283
284 assert s['protocol'] is not None
284 assert s['protocol'] is not None
285 assert s['ctxoptions'] is not None
285 assert s['ctxoptions'] is not None
286 assert s['verifymode'] is not None
286 assert s['verifymode'] is not None
287
287
288 return s
288 return s
289
289
290 def protocolsettings(protocol):
290 def protocolsettings(protocol):
291 """Resolve the protocol for a config value.
291 """Resolve the protocol for a config value.
292
292
293 Returns a 3-tuple of (protocol, options, ui value) where the first
293 Returns a 3-tuple of (protocol, options, ui value) where the first
294 2 items are values used by SSLContext and the last is a string value
294 2 items are values used by SSLContext and the last is a string value
295 of the ``minimumprotocol`` config option equivalent.
295 of the ``minimumprotocol`` config option equivalent.
296 """
296 """
297 if protocol not in configprotocols:
297 if protocol not in configprotocols:
298 raise ValueError('protocol value not supported: %s' % protocol)
298 raise ValueError('protocol value not supported: %s' % protocol)
299
299
300 # Despite its name, PROTOCOL_SSLv23 selects the highest protocol
300 # Despite its name, PROTOCOL_SSLv23 selects the highest protocol
301 # that both ends support, including TLS protocols. On legacy stacks,
301 # that both ends support, including TLS protocols. On legacy stacks,
302 # the highest it likely goes is TLS 1.0. On modern stacks, it can
302 # the highest it likely goes is TLS 1.0. On modern stacks, it can
303 # support TLS 1.2.
303 # support TLS 1.2.
304 #
304 #
305 # The PROTOCOL_TLSv* constants select a specific TLS version
305 # The PROTOCOL_TLSv* constants select a specific TLS version
306 # only (as opposed to multiple versions). So the method for
306 # only (as opposed to multiple versions). So the method for
307 # supporting multiple TLS versions is to use PROTOCOL_SSLv23 and
307 # supporting multiple TLS versions is to use PROTOCOL_SSLv23 and
308 # disable protocols via SSLContext.options and OP_NO_* constants.
308 # disable protocols via SSLContext.options and OP_NO_* constants.
309 # However, SSLContext.options doesn't work unless we have the
309 # However, SSLContext.options doesn't work unless we have the
310 # full/real SSLContext available to us.
310 # full/real SSLContext available to us.
311 if supportedprotocols == set(['tls1.0']):
311 if supportedprotocols == set(['tls1.0']):
312 if protocol != 'tls1.0':
312 if protocol != 'tls1.0':
313 raise error.Abort(_('current Python does not support protocol '
313 raise error.Abort(_('current Python does not support protocol '
314 'setting %s') % protocol,
314 'setting %s') % protocol,
315 hint=_('upgrade Python or disable setting since '
315 hint=_('upgrade Python or disable setting since '
316 'only TLS 1.0 is supported'))
316 'only TLS 1.0 is supported'))
317
317
318 return ssl.PROTOCOL_TLSv1, 0, 'tls1.0'
318 return ssl.PROTOCOL_TLSv1, 0, 'tls1.0'
319
319
320 # WARNING: returned options don't work unless the modern ssl module
320 # WARNING: returned options don't work unless the modern ssl module
321 # is available. Be careful when adding options here.
321 # is available. Be careful when adding options here.
322
322
323 # SSLv2 and SSLv3 are broken. We ban them outright.
323 # SSLv2 and SSLv3 are broken. We ban them outright.
324 options = ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3
324 options = ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3
325
325
326 if protocol == 'tls1.0':
326 if protocol == 'tls1.0':
327 # Defaults above are to use TLS 1.0+
327 # Defaults above are to use TLS 1.0+
328 pass
328 pass
329 elif protocol == 'tls1.1':
329 elif protocol == 'tls1.1':
330 options |= ssl.OP_NO_TLSv1
330 options |= ssl.OP_NO_TLSv1
331 elif protocol == 'tls1.2':
331 elif protocol == 'tls1.2':
332 options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
332 options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
333 else:
333 else:
334 raise error.Abort(_('this should not happen'))
334 raise error.Abort(_('this should not happen'))
335
335
336 # Prevent CRIME.
336 # Prevent CRIME.
337 # There is no guarantee this attribute is defined on the module.
337 # There is no guarantee this attribute is defined on the module.
338 options |= getattr(ssl, 'OP_NO_COMPRESSION', 0)
338 options |= getattr(ssl, 'OP_NO_COMPRESSION', 0)
339
339
340 return ssl.PROTOCOL_SSLv23, options, protocol
340 return ssl.PROTOCOL_SSLv23, options, protocol
341
341
342 def wrapsocket(sock, keyfile, certfile, ui, serverhostname=None):
342 def wrapsocket(sock, keyfile, certfile, ui, serverhostname=None):
343 """Add SSL/TLS to a socket.
343 """Add SSL/TLS to a socket.
344
344
345 This is a glorified wrapper for ``ssl.wrap_socket()``. It makes sane
345 This is a glorified wrapper for ``ssl.wrap_socket()``. It makes sane
346 choices based on what security options are available.
346 choices based on what security options are available.
347
347
348 In addition to the arguments supported by ``ssl.wrap_socket``, we allow
348 In addition to the arguments supported by ``ssl.wrap_socket``, we allow
349 the following additional arguments:
349 the following additional arguments:
350
350
351 * serverhostname - The expected hostname of the remote server. If the
351 * serverhostname - The expected hostname of the remote server. If the
352 server (and client) support SNI, this tells the server which certificate
352 server (and client) support SNI, this tells the server which certificate
353 to use.
353 to use.
354 """
354 """
355 if not serverhostname:
355 if not serverhostname:
356 raise error.Abort(_('serverhostname argument is required'))
356 raise error.Abort(_('serverhostname argument is required'))
357
357
358 settings = _hostsettings(ui, serverhostname)
358 settings = _hostsettings(ui, serverhostname)
359
359
360 # We can't use ssl.create_default_context() because it calls
360 # We can't use ssl.create_default_context() because it calls
361 # load_default_certs() unless CA arguments are passed to it. We want to
361 # load_default_certs() unless CA arguments are passed to it. We want to
362 # have explicit control over CA loading because implicitly loading
362 # have explicit control over CA loading because implicitly loading
363 # CAs may undermine the user's intent. For example, a user may define a CA
363 # CAs may undermine the user's intent. For example, a user may define a CA
364 # bundle with a specific CA cert removed. If the system/default CA bundle
364 # bundle with a specific CA cert removed. If the system/default CA bundle
365 # is loaded and contains that removed CA, you've just undone the user's
365 # is loaded and contains that removed CA, you've just undone the user's
366 # choice.
366 # choice.
367 sslcontext = SSLContext(settings['protocol'])
367 sslcontext = SSLContext(settings['protocol'])
368
368
369 # This is a no-op unless using modern ssl.
369 # This is a no-op unless using modern ssl.
370 sslcontext.options |= settings['ctxoptions']
370 sslcontext.options |= settings['ctxoptions']
371
371
372 # This still works on our fake SSLContext.
372 # This still works on our fake SSLContext.
373 sslcontext.verify_mode = settings['verifymode']
373 sslcontext.verify_mode = settings['verifymode']
374
374
375 if settings['ciphers']:
375 if settings['ciphers']:
376 try:
376 try:
377 sslcontext.set_ciphers(settings['ciphers'])
377 sslcontext.set_ciphers(settings['ciphers'])
378 except ssl.SSLError as e:
378 except ssl.SSLError as e:
379 raise error.Abort(_('could not set ciphers: %s') % e.args[0],
379 raise error.Abort(_('could not set ciphers: %s') % e.args[0],
380 hint=_('change cipher string (%s) in config') %
380 hint=_('change cipher string (%s) in config') %
381 settings['ciphers'])
381 settings['ciphers'])
382
382
383 if certfile is not None:
383 if certfile is not None:
384 def password():
384 def password():
385 f = keyfile or certfile
385 f = keyfile or certfile
386 return ui.getpass(_('passphrase for %s: ') % f, '')
386 return ui.getpass(_('passphrase for %s: ') % f, '')
387 sslcontext.load_cert_chain(certfile, keyfile, password)
387 sslcontext.load_cert_chain(certfile, keyfile, password)
388
388
389 if settings['cafile'] is not None:
389 if settings['cafile'] is not None:
390 try:
390 try:
391 sslcontext.load_verify_locations(cafile=settings['cafile'])
391 sslcontext.load_verify_locations(cafile=settings['cafile'])
392 except ssl.SSLError as e:
392 except ssl.SSLError as e:
393 raise error.Abort(_('error loading CA file %s: %s') % (
393 raise error.Abort(_('error loading CA file %s: %s') % (
394 settings['cafile'], e.args[1]),
394 settings['cafile'], e.args[1]),
395 hint=_('file is empty or malformed?'))
395 hint=_('file is empty or malformed?'))
396 caloaded = True
396 caloaded = True
397 elif settings['allowloaddefaultcerts']:
397 elif settings['allowloaddefaultcerts']:
398 # This is a no-op on old Python.
398 # This is a no-op on old Python.
399 sslcontext.load_default_certs()
399 sslcontext.load_default_certs()
400 caloaded = True
400 caloaded = True
401 else:
401 else:
402 caloaded = False
402 caloaded = False
403
403
404 try:
404 try:
405 sslsocket = sslcontext.wrap_socket(sock, server_hostname=serverhostname)
405 sslsocket = sslcontext.wrap_socket(sock, server_hostname=serverhostname)
406 except ssl.SSLError as e:
406 except ssl.SSLError as e:
407 # If we're doing certificate verification and no CA certs are loaded,
407 # If we're doing certificate verification and no CA certs are loaded,
408 # that is almost certainly the reason why verification failed. Provide
408 # that is almost certainly the reason why verification failed. Provide
409 # a hint to the user.
409 # a hint to the user.
410 # Only modern ssl module exposes SSLContext.get_ca_certs() so we can
410 # Only modern ssl module exposes SSLContext.get_ca_certs() so we can
411 # only show this warning if modern ssl is available.
411 # only show this warning if modern ssl is available.
412 if (caloaded and settings['verifymode'] == ssl.CERT_REQUIRED and
412 if (caloaded and settings['verifymode'] == ssl.CERT_REQUIRED and
413 modernssl and not sslcontext.get_ca_certs()):
413 modernssl and not sslcontext.get_ca_certs()):
414 ui.warn(_('(an attempt was made to load CA certificates but none '
414 ui.warn(_('(an attempt was made to load CA certificates but none '
415 'were loaded; see '
415 'were loaded; see '
416 'https://mercurial-scm.org/wiki/SecureConnections for '
416 'https://mercurial-scm.org/wiki/SecureConnections for '
417 'how to configure Mercurial to avoid this error)\n'))
417 'how to configure Mercurial to avoid this error)\n'))
418 # Try to print more helpful error messages for known failures.
418 # Try to print more helpful error messages for known failures.
419 if util.safehasattr(e, 'reason'):
419 if util.safehasattr(e, 'reason'):
420 # This error occurs when the client and server don't share a
421 # common/supported SSL/TLS protocol. We've disabled SSLv2 and SSLv3
422 # outright. Hopefully the reason for this error is that we require
423 # TLS 1.1+ and the server only supports TLS 1.0. Whatever the
424 # reason, try to emit an actionable warning.
420 if e.reason == 'UNSUPPORTED_PROTOCOL':
425 if e.reason == 'UNSUPPORTED_PROTOCOL':
421 ui.warn(_('(could not negotiate a common protocol; see '
426 # We attempted TLS 1.0+.
422 'https://mercurial-scm.org/wiki/SecureConnections '
427 if settings['protocolui'] == 'tls1.0':
423 'for how to configure Mercurial to avoid this '
428 # We support more than just TLS 1.0+. If this happens,
424 'error)\n'))
429 # the likely scenario is either the client or the server
430 # is really old. (e.g. server doesn't support TLS 1.0+ or
431 # client doesn't support modern TLS versions introduced
432 # several years from when this comment was written).
433 if supportedprotocols != set(['tls1.0']):
434 ui.warn(_(
435 '(could not communicate with %s using security '
436 'protocols %s; if you are using a modern Mercurial '
437 'version, consider contacting the operator of this '
438 'server; see '
439 'https://mercurial-scm.org/wiki/SecureConnections '
440 'for more info)\n') % (
441 serverhostname,
442 ', '.join(sorted(supportedprotocols))))
443 else:
444 ui.warn(_(
445 '(could not communicate with %s using TLS 1.0; the '
446 'likely cause of this is the server no longer '
447 'supports TLS 1.0 because it has known security '
448 'vulnerabilities; see '
449 'https://mercurial-scm.org/wiki/SecureConnections '
450 'for more info)\n') % serverhostname)
451 else:
452 # We attempted TLS 1.1+. We can only get here if the client
453 # supports the configured protocol. So the likely reason is
454 # the client wants better security than the server can
455 # offer.
456 ui.warn(_(
457 '(could not negotiate a common security protocol (%s+) '
458 'with %s; the likely cause is Mercurial is configured '
459 'to be more secure than the server can support)\n') % (
460 settings['protocolui'], serverhostname))
461 ui.warn(_('(consider contacting the operator of this '
462 'server and ask them to support modern TLS '
463 'protocol versions; or, set '
464 'hostsecurity.%s:minimumprotocol=tls1.0 to allow '
465 'use of legacy, less secure protocols when '
466 'communicating with this server)\n') %
467 serverhostname)
468 ui.warn(_(
469 '(see https://mercurial-scm.org/wiki/SecureConnections '
470 'for more info)\n'))
425 raise
471 raise
426
472
427 # check if wrap_socket failed silently because socket had been
473 # check if wrap_socket failed silently because socket had been
428 # closed
474 # closed
429 # - see http://bugs.python.org/issue13721
475 # - see http://bugs.python.org/issue13721
430 if not sslsocket.cipher():
476 if not sslsocket.cipher():
431 raise error.Abort(_('ssl connection failed'))
477 raise error.Abort(_('ssl connection failed'))
432
478
433 sslsocket._hgstate = {
479 sslsocket._hgstate = {
434 'caloaded': caloaded,
480 'caloaded': caloaded,
435 'hostname': serverhostname,
481 'hostname': serverhostname,
436 'settings': settings,
482 'settings': settings,
437 'ui': ui,
483 'ui': ui,
438 }
484 }
439
485
440 return sslsocket
486 return sslsocket
441
487
442 def wrapserversocket(sock, ui, certfile=None, keyfile=None, cafile=None,
488 def wrapserversocket(sock, ui, certfile=None, keyfile=None, cafile=None,
443 requireclientcert=False):
489 requireclientcert=False):
444 """Wrap a socket for use by servers.
490 """Wrap a socket for use by servers.
445
491
446 ``certfile`` and ``keyfile`` specify the files containing the certificate's
492 ``certfile`` and ``keyfile`` specify the files containing the certificate's
447 public and private keys, respectively. Both keys can be defined in the same
493 public and private keys, respectively. Both keys can be defined in the same
448 file via ``certfile`` (the private key must come first in the file).
494 file via ``certfile`` (the private key must come first in the file).
449
495
450 ``cafile`` defines the path to certificate authorities.
496 ``cafile`` defines the path to certificate authorities.
451
497
452 ``requireclientcert`` specifies whether to require client certificates.
498 ``requireclientcert`` specifies whether to require client certificates.
453
499
454 Typically ``cafile`` is only defined if ``requireclientcert`` is true.
500 Typically ``cafile`` is only defined if ``requireclientcert`` is true.
455 """
501 """
456 protocol, options, _protocolui = protocolsettings('tls1.0')
502 protocol, options, _protocolui = protocolsettings('tls1.0')
457
503
458 # This config option is intended for use in tests only. It is a giant
504 # This config option is intended for use in tests only. It is a giant
459 # footgun to kill security. Don't define it.
505 # footgun to kill security. Don't define it.
460 exactprotocol = ui.config('devel', 'serverexactprotocol')
506 exactprotocol = ui.config('devel', 'serverexactprotocol')
461 if exactprotocol == 'tls1.0':
507 if exactprotocol == 'tls1.0':
462 protocol = ssl.PROTOCOL_TLSv1
508 protocol = ssl.PROTOCOL_TLSv1
463 elif exactprotocol == 'tls1.1':
509 elif exactprotocol == 'tls1.1':
464 if 'tls1.1' not in supportedprotocols:
510 if 'tls1.1' not in supportedprotocols:
465 raise error.Abort(_('TLS 1.1 not supported by this Python'))
511 raise error.Abort(_('TLS 1.1 not supported by this Python'))
466 protocol = ssl.PROTOCOL_TLSv1_1
512 protocol = ssl.PROTOCOL_TLSv1_1
467 elif exactprotocol == 'tls1.2':
513 elif exactprotocol == 'tls1.2':
468 if 'tls1.2' not in supportedprotocols:
514 if 'tls1.2' not in supportedprotocols:
469 raise error.Abort(_('TLS 1.2 not supported by this Python'))
515 raise error.Abort(_('TLS 1.2 not supported by this Python'))
470 protocol = ssl.PROTOCOL_TLSv1_2
516 protocol = ssl.PROTOCOL_TLSv1_2
471 elif exactprotocol:
517 elif exactprotocol:
472 raise error.Abort(_('invalid value for serverexactprotocol: %s') %
518 raise error.Abort(_('invalid value for serverexactprotocol: %s') %
473 exactprotocol)
519 exactprotocol)
474
520
475 if modernssl:
521 if modernssl:
476 # We /could/ use create_default_context() here since it doesn't load
522 # We /could/ use create_default_context() here since it doesn't load
477 # CAs when configured for client auth. However, it is hard-coded to
523 # CAs when configured for client auth. However, it is hard-coded to
478 # use ssl.PROTOCOL_SSLv23 which may not be appropriate here.
524 # use ssl.PROTOCOL_SSLv23 which may not be appropriate here.
479 sslcontext = SSLContext(protocol)
525 sslcontext = SSLContext(protocol)
480 sslcontext.options |= options
526 sslcontext.options |= options
481
527
482 # Improve forward secrecy.
528 # Improve forward secrecy.
483 sslcontext.options |= getattr(ssl, 'OP_SINGLE_DH_USE', 0)
529 sslcontext.options |= getattr(ssl, 'OP_SINGLE_DH_USE', 0)
484 sslcontext.options |= getattr(ssl, 'OP_SINGLE_ECDH_USE', 0)
530 sslcontext.options |= getattr(ssl, 'OP_SINGLE_ECDH_USE', 0)
485
531
486 # Use the list of more secure ciphers if found in the ssl module.
532 # Use the list of more secure ciphers if found in the ssl module.
487 if util.safehasattr(ssl, '_RESTRICTED_SERVER_CIPHERS'):
533 if util.safehasattr(ssl, '_RESTRICTED_SERVER_CIPHERS'):
488 sslcontext.options |= getattr(ssl, 'OP_CIPHER_SERVER_PREFERENCE', 0)
534 sslcontext.options |= getattr(ssl, 'OP_CIPHER_SERVER_PREFERENCE', 0)
489 sslcontext.set_ciphers(ssl._RESTRICTED_SERVER_CIPHERS)
535 sslcontext.set_ciphers(ssl._RESTRICTED_SERVER_CIPHERS)
490 else:
536 else:
491 sslcontext = SSLContext(ssl.PROTOCOL_TLSv1)
537 sslcontext = SSLContext(ssl.PROTOCOL_TLSv1)
492
538
493 if requireclientcert:
539 if requireclientcert:
494 sslcontext.verify_mode = ssl.CERT_REQUIRED
540 sslcontext.verify_mode = ssl.CERT_REQUIRED
495 else:
541 else:
496 sslcontext.verify_mode = ssl.CERT_NONE
542 sslcontext.verify_mode = ssl.CERT_NONE
497
543
498 if certfile or keyfile:
544 if certfile or keyfile:
499 sslcontext.load_cert_chain(certfile=certfile, keyfile=keyfile)
545 sslcontext.load_cert_chain(certfile=certfile, keyfile=keyfile)
500
546
501 if cafile:
547 if cafile:
502 sslcontext.load_verify_locations(cafile=cafile)
548 sslcontext.load_verify_locations(cafile=cafile)
503
549
504 return sslcontext.wrap_socket(sock, server_side=True)
550 return sslcontext.wrap_socket(sock, server_side=True)
505
551
506 class wildcarderror(Exception):
552 class wildcarderror(Exception):
507 """Represents an error parsing wildcards in DNS name."""
553 """Represents an error parsing wildcards in DNS name."""
508
554
509 def _dnsnamematch(dn, hostname, maxwildcards=1):
555 def _dnsnamematch(dn, hostname, maxwildcards=1):
510 """Match DNS names according RFC 6125 section 6.4.3.
556 """Match DNS names according RFC 6125 section 6.4.3.
511
557
512 This code is effectively copied from CPython's ssl._dnsname_match.
558 This code is effectively copied from CPython's ssl._dnsname_match.
513
559
514 Returns a bool indicating whether the expected hostname matches
560 Returns a bool indicating whether the expected hostname matches
515 the value in ``dn``.
561 the value in ``dn``.
516 """
562 """
517 pats = []
563 pats = []
518 if not dn:
564 if not dn:
519 return False
565 return False
520
566
521 pieces = dn.split(r'.')
567 pieces = dn.split(r'.')
522 leftmost = pieces[0]
568 leftmost = pieces[0]
523 remainder = pieces[1:]
569 remainder = pieces[1:]
524 wildcards = leftmost.count('*')
570 wildcards = leftmost.count('*')
525 if wildcards > maxwildcards:
571 if wildcards > maxwildcards:
526 raise wildcarderror(
572 raise wildcarderror(
527 _('too many wildcards in certificate DNS name: %s') % dn)
573 _('too many wildcards in certificate DNS name: %s') % dn)
528
574
529 # speed up common case w/o wildcards
575 # speed up common case w/o wildcards
530 if not wildcards:
576 if not wildcards:
531 return dn.lower() == hostname.lower()
577 return dn.lower() == hostname.lower()
532
578
533 # RFC 6125, section 6.4.3, subitem 1.
579 # RFC 6125, section 6.4.3, subitem 1.
534 # The client SHOULD NOT attempt to match a presented identifier in which
580 # The client SHOULD NOT attempt to match a presented identifier in which
535 # the wildcard character comprises a label other than the left-most label.
581 # the wildcard character comprises a label other than the left-most label.
536 if leftmost == '*':
582 if leftmost == '*':
537 # When '*' is a fragment by itself, it matches a non-empty dotless
583 # When '*' is a fragment by itself, it matches a non-empty dotless
538 # fragment.
584 # fragment.
539 pats.append('[^.]+')
585 pats.append('[^.]+')
540 elif leftmost.startswith('xn--') or hostname.startswith('xn--'):
586 elif leftmost.startswith('xn--') or hostname.startswith('xn--'):
541 # RFC 6125, section 6.4.3, subitem 3.
587 # RFC 6125, section 6.4.3, subitem 3.
542 # The client SHOULD NOT attempt to match a presented identifier
588 # The client SHOULD NOT attempt to match a presented identifier
543 # where the wildcard character is embedded within an A-label or
589 # where the wildcard character is embedded within an A-label or
544 # U-label of an internationalized domain name.
590 # U-label of an internationalized domain name.
545 pats.append(re.escape(leftmost))
591 pats.append(re.escape(leftmost))
546 else:
592 else:
547 # Otherwise, '*' matches any dotless string, e.g. www*
593 # Otherwise, '*' matches any dotless string, e.g. www*
548 pats.append(re.escape(leftmost).replace(r'\*', '[^.]*'))
594 pats.append(re.escape(leftmost).replace(r'\*', '[^.]*'))
549
595
550 # add the remaining fragments, ignore any wildcards
596 # add the remaining fragments, ignore any wildcards
551 for frag in remainder:
597 for frag in remainder:
552 pats.append(re.escape(frag))
598 pats.append(re.escape(frag))
553
599
554 pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)
600 pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)
555 return pat.match(hostname) is not None
601 return pat.match(hostname) is not None
556
602
557 def _verifycert(cert, hostname):
603 def _verifycert(cert, hostname):
558 '''Verify that cert (in socket.getpeercert() format) matches hostname.
604 '''Verify that cert (in socket.getpeercert() format) matches hostname.
559 CRLs is not handled.
605 CRLs is not handled.
560
606
561 Returns error message if any problems are found and None on success.
607 Returns error message if any problems are found and None on success.
562 '''
608 '''
563 if not cert:
609 if not cert:
564 return _('no certificate received')
610 return _('no certificate received')
565
611
566 dnsnames = []
612 dnsnames = []
567 san = cert.get('subjectAltName', [])
613 san = cert.get('subjectAltName', [])
568 for key, value in san:
614 for key, value in san:
569 if key == 'DNS':
615 if key == 'DNS':
570 try:
616 try:
571 if _dnsnamematch(value, hostname):
617 if _dnsnamematch(value, hostname):
572 return
618 return
573 except wildcarderror as e:
619 except wildcarderror as e:
574 return e.args[0]
620 return e.args[0]
575
621
576 dnsnames.append(value)
622 dnsnames.append(value)
577
623
578 if not dnsnames:
624 if not dnsnames:
579 # The subject is only checked when there is no DNS in subjectAltName.
625 # The subject is only checked when there is no DNS in subjectAltName.
580 for sub in cert.get('subject', []):
626 for sub in cert.get('subject', []):
581 for key, value in sub:
627 for key, value in sub:
582 # According to RFC 2818 the most specific Common Name must
628 # According to RFC 2818 the most specific Common Name must
583 # be used.
629 # be used.
584 if key == 'commonName':
630 if key == 'commonName':
585 # 'subject' entries are unicide.
631 # 'subject' entries are unicide.
586 try:
632 try:
587 value = value.encode('ascii')
633 value = value.encode('ascii')
588 except UnicodeEncodeError:
634 except UnicodeEncodeError:
589 return _('IDN in certificate not supported')
635 return _('IDN in certificate not supported')
590
636
591 try:
637 try:
592 if _dnsnamematch(value, hostname):
638 if _dnsnamematch(value, hostname):
593 return
639 return
594 except wildcarderror as e:
640 except wildcarderror as e:
595 return e.args[0]
641 return e.args[0]
596
642
597 dnsnames.append(value)
643 dnsnames.append(value)
598
644
599 if len(dnsnames) > 1:
645 if len(dnsnames) > 1:
600 return _('certificate is for %s') % ', '.join(dnsnames)
646 return _('certificate is for %s') % ', '.join(dnsnames)
601 elif len(dnsnames) == 1:
647 elif len(dnsnames) == 1:
602 return _('certificate is for %s') % dnsnames[0]
648 return _('certificate is for %s') % dnsnames[0]
603 else:
649 else:
604 return _('no commonName or subjectAltName found in certificate')
650 return _('no commonName or subjectAltName found in certificate')
605
651
606 def _plainapplepython():
652 def _plainapplepython():
607 """return true if this seems to be a pure Apple Python that
653 """return true if this seems to be a pure Apple Python that
608 * is unfrozen and presumably has the whole mercurial module in the file
654 * is unfrozen and presumably has the whole mercurial module in the file
609 system
655 system
610 * presumably is an Apple Python that uses Apple OpenSSL which has patches
656 * presumably is an Apple Python that uses Apple OpenSSL which has patches
611 for using system certificate store CAs in addition to the provided
657 for using system certificate store CAs in addition to the provided
612 cacerts file
658 cacerts file
613 """
659 """
614 if sys.platform != 'darwin' or util.mainfrozen() or not sys.executable:
660 if sys.platform != 'darwin' or util.mainfrozen() or not sys.executable:
615 return False
661 return False
616 exe = os.path.realpath(sys.executable).lower()
662 exe = os.path.realpath(sys.executable).lower()
617 return (exe.startswith('/usr/bin/python') or
663 return (exe.startswith('/usr/bin/python') or
618 exe.startswith('/system/library/frameworks/python.framework/'))
664 exe.startswith('/system/library/frameworks/python.framework/'))
619
665
620 _systemcacertpaths = [
666 _systemcacertpaths = [
621 # RHEL, CentOS, and Fedora
667 # RHEL, CentOS, and Fedora
622 '/etc/pki/tls/certs/ca-bundle.trust.crt',
668 '/etc/pki/tls/certs/ca-bundle.trust.crt',
623 # Debian, Ubuntu, Gentoo
669 # Debian, Ubuntu, Gentoo
624 '/etc/ssl/certs/ca-certificates.crt',
670 '/etc/ssl/certs/ca-certificates.crt',
625 ]
671 ]
626
672
627 def _defaultcacerts(ui):
673 def _defaultcacerts(ui):
628 """return path to default CA certificates or None.
674 """return path to default CA certificates or None.
629
675
630 It is assumed this function is called when the returned certificates
676 It is assumed this function is called when the returned certificates
631 file will actually be used to validate connections. Therefore this
677 file will actually be used to validate connections. Therefore this
632 function may print warnings or debug messages assuming this usage.
678 function may print warnings or debug messages assuming this usage.
633
679
634 We don't print a message when the Python is able to load default
680 We don't print a message when the Python is able to load default
635 CA certs because this scenario is detected at socket connect time.
681 CA certs because this scenario is detected at socket connect time.
636 """
682 """
637 # The "certifi" Python package provides certificates. If it is installed,
683 # The "certifi" Python package provides certificates. If it is installed,
638 # assume the user intends it to be used and use it.
684 # assume the user intends it to be used and use it.
639 try:
685 try:
640 import certifi
686 import certifi
641 certs = certifi.where()
687 certs = certifi.where()
642 ui.debug('using ca certificates from certifi\n')
688 ui.debug('using ca certificates from certifi\n')
643 return certs
689 return certs
644 except ImportError:
690 except ImportError:
645 pass
691 pass
646
692
647 # On Windows, only the modern ssl module is capable of loading the system
693 # On Windows, only the modern ssl module is capable of loading the system
648 # CA certificates. If we're not capable of doing that, emit a warning
694 # CA certificates. If we're not capable of doing that, emit a warning
649 # because we'll get a certificate verification error later and the lack
695 # because we'll get a certificate verification error later and the lack
650 # of loaded CA certificates will be the reason why.
696 # of loaded CA certificates will be the reason why.
651 # Assertion: this code is only called if certificates are being verified.
697 # Assertion: this code is only called if certificates are being verified.
652 if os.name == 'nt':
698 if os.name == 'nt':
653 if not _canloaddefaultcerts:
699 if not _canloaddefaultcerts:
654 ui.warn(_('(unable to load Windows CA certificates; see '
700 ui.warn(_('(unable to load Windows CA certificates; see '
655 'https://mercurial-scm.org/wiki/SecureConnections for '
701 'https://mercurial-scm.org/wiki/SecureConnections for '
656 'how to configure Mercurial to avoid this message)\n'))
702 'how to configure Mercurial to avoid this message)\n'))
657
703
658 return None
704 return None
659
705
660 # Apple's OpenSSL has patches that allow a specially constructed certificate
706 # Apple's OpenSSL has patches that allow a specially constructed certificate
661 # to load the system CA store. If we're running on Apple Python, use this
707 # to load the system CA store. If we're running on Apple Python, use this
662 # trick.
708 # trick.
663 if _plainapplepython():
709 if _plainapplepython():
664 dummycert = os.path.join(os.path.dirname(__file__), 'dummycert.pem')
710 dummycert = os.path.join(os.path.dirname(__file__), 'dummycert.pem')
665 if os.path.exists(dummycert):
711 if os.path.exists(dummycert):
666 return dummycert
712 return dummycert
667
713
668 # The Apple OpenSSL trick isn't available to us. If Python isn't able to
714 # The Apple OpenSSL trick isn't available to us. If Python isn't able to
669 # load system certs, we're out of luck.
715 # load system certs, we're out of luck.
670 if sys.platform == 'darwin':
716 if sys.platform == 'darwin':
671 # FUTURE Consider looking for Homebrew or MacPorts installed certs
717 # FUTURE Consider looking for Homebrew or MacPorts installed certs
672 # files. Also consider exporting the keychain certs to a file during
718 # files. Also consider exporting the keychain certs to a file during
673 # Mercurial install.
719 # Mercurial install.
674 if not _canloaddefaultcerts:
720 if not _canloaddefaultcerts:
675 ui.warn(_('(unable to load CA certificates; see '
721 ui.warn(_('(unable to load CA certificates; see '
676 'https://mercurial-scm.org/wiki/SecureConnections for '
722 'https://mercurial-scm.org/wiki/SecureConnections for '
677 'how to configure Mercurial to avoid this message)\n'))
723 'how to configure Mercurial to avoid this message)\n'))
678 return None
724 return None
679
725
680 # / is writable on Windows. Out of an abundance of caution make sure
726 # / is writable on Windows. Out of an abundance of caution make sure
681 # we're not on Windows because paths from _systemcacerts could be installed
727 # we're not on Windows because paths from _systemcacerts could be installed
682 # by non-admin users.
728 # by non-admin users.
683 assert os.name != 'nt'
729 assert os.name != 'nt'
684
730
685 # Try to find CA certificates in well-known locations. We print a warning
731 # Try to find CA certificates in well-known locations. We print a warning
686 # when using a found file because we don't want too much silent magic
732 # when using a found file because we don't want too much silent magic
687 # for security settings. The expectation is that proper Mercurial
733 # for security settings. The expectation is that proper Mercurial
688 # installs will have the CA certs path defined at install time and the
734 # installs will have the CA certs path defined at install time and the
689 # installer/packager will make an appropriate decision on the user's
735 # installer/packager will make an appropriate decision on the user's
690 # behalf. We only get here and perform this setting as a feature of
736 # behalf. We only get here and perform this setting as a feature of
691 # last resort.
737 # last resort.
692 if not _canloaddefaultcerts:
738 if not _canloaddefaultcerts:
693 for path in _systemcacertpaths:
739 for path in _systemcacertpaths:
694 if os.path.isfile(path):
740 if os.path.isfile(path):
695 ui.warn(_('(using CA certificates from %s; if you see this '
741 ui.warn(_('(using CA certificates from %s; if you see this '
696 'message, your Mercurial install is not properly '
742 'message, your Mercurial install is not properly '
697 'configured; see '
743 'configured; see '
698 'https://mercurial-scm.org/wiki/SecureConnections '
744 'https://mercurial-scm.org/wiki/SecureConnections '
699 'for how to configure Mercurial to avoid this '
745 'for how to configure Mercurial to avoid this '
700 'message)\n') % path)
746 'message)\n') % path)
701 return path
747 return path
702
748
703 ui.warn(_('(unable to load CA certificates; see '
749 ui.warn(_('(unable to load CA certificates; see '
704 'https://mercurial-scm.org/wiki/SecureConnections for '
750 'https://mercurial-scm.org/wiki/SecureConnections for '
705 'how to configure Mercurial to avoid this message)\n'))
751 'how to configure Mercurial to avoid this message)\n'))
706
752
707 return None
753 return None
708
754
709 def validatesocket(sock):
755 def validatesocket(sock):
710 """Validate a socket meets security requiremnets.
756 """Validate a socket meets security requiremnets.
711
757
712 The passed socket must have been created with ``wrapsocket()``.
758 The passed socket must have been created with ``wrapsocket()``.
713 """
759 """
714 host = sock._hgstate['hostname']
760 host = sock._hgstate['hostname']
715 ui = sock._hgstate['ui']
761 ui = sock._hgstate['ui']
716 settings = sock._hgstate['settings']
762 settings = sock._hgstate['settings']
717
763
718 try:
764 try:
719 peercert = sock.getpeercert(True)
765 peercert = sock.getpeercert(True)
720 peercert2 = sock.getpeercert()
766 peercert2 = sock.getpeercert()
721 except AttributeError:
767 except AttributeError:
722 raise error.Abort(_('%s ssl connection error') % host)
768 raise error.Abort(_('%s ssl connection error') % host)
723
769
724 if not peercert:
770 if not peercert:
725 raise error.Abort(_('%s certificate error: '
771 raise error.Abort(_('%s certificate error: '
726 'no certificate received') % host)
772 'no certificate received') % host)
727
773
728 if settings['disablecertverification']:
774 if settings['disablecertverification']:
729 # We don't print the certificate fingerprint because it shouldn't
775 # We don't print the certificate fingerprint because it shouldn't
730 # be necessary: if the user requested certificate verification be
776 # be necessary: if the user requested certificate verification be
731 # disabled, they presumably already saw a message about the inability
777 # disabled, they presumably already saw a message about the inability
732 # to verify the certificate and this message would have printed the
778 # to verify the certificate and this message would have printed the
733 # fingerprint. So printing the fingerprint here adds little to no
779 # fingerprint. So printing the fingerprint here adds little to no
734 # value.
780 # value.
735 ui.warn(_('warning: connection security to %s is disabled per current '
781 ui.warn(_('warning: connection security to %s is disabled per current '
736 'settings; communication is susceptible to eavesdropping '
782 'settings; communication is susceptible to eavesdropping '
737 'and tampering\n') % host)
783 'and tampering\n') % host)
738 return
784 return
739
785
740 # If a certificate fingerprint is pinned, use it and only it to
786 # If a certificate fingerprint is pinned, use it and only it to
741 # validate the remote cert.
787 # validate the remote cert.
742 peerfingerprints = {
788 peerfingerprints = {
743 'sha1': hashlib.sha1(peercert).hexdigest(),
789 'sha1': hashlib.sha1(peercert).hexdigest(),
744 'sha256': hashlib.sha256(peercert).hexdigest(),
790 'sha256': hashlib.sha256(peercert).hexdigest(),
745 'sha512': hashlib.sha512(peercert).hexdigest(),
791 'sha512': hashlib.sha512(peercert).hexdigest(),
746 }
792 }
747
793
748 def fmtfingerprint(s):
794 def fmtfingerprint(s):
749 return ':'.join([s[x:x + 2] for x in range(0, len(s), 2)])
795 return ':'.join([s[x:x + 2] for x in range(0, len(s), 2)])
750
796
751 nicefingerprint = 'sha256:%s' % fmtfingerprint(peerfingerprints['sha256'])
797 nicefingerprint = 'sha256:%s' % fmtfingerprint(peerfingerprints['sha256'])
752
798
753 if settings['certfingerprints']:
799 if settings['certfingerprints']:
754 for hash, fingerprint in settings['certfingerprints']:
800 for hash, fingerprint in settings['certfingerprints']:
755 if peerfingerprints[hash].lower() == fingerprint:
801 if peerfingerprints[hash].lower() == fingerprint:
756 ui.debug('%s certificate matched fingerprint %s:%s\n' %
802 ui.debug('%s certificate matched fingerprint %s:%s\n' %
757 (host, hash, fmtfingerprint(fingerprint)))
803 (host, hash, fmtfingerprint(fingerprint)))
758 return
804 return
759
805
760 # Pinned fingerprint didn't match. This is a fatal error.
806 # Pinned fingerprint didn't match. This is a fatal error.
761 if settings['legacyfingerprint']:
807 if settings['legacyfingerprint']:
762 section = 'hostfingerprint'
808 section = 'hostfingerprint'
763 nice = fmtfingerprint(peerfingerprints['sha1'])
809 nice = fmtfingerprint(peerfingerprints['sha1'])
764 else:
810 else:
765 section = 'hostsecurity'
811 section = 'hostsecurity'
766 nice = '%s:%s' % (hash, fmtfingerprint(peerfingerprints[hash]))
812 nice = '%s:%s' % (hash, fmtfingerprint(peerfingerprints[hash]))
767 raise error.Abort(_('certificate for %s has unexpected '
813 raise error.Abort(_('certificate for %s has unexpected '
768 'fingerprint %s') % (host, nice),
814 'fingerprint %s') % (host, nice),
769 hint=_('check %s configuration') % section)
815 hint=_('check %s configuration') % section)
770
816
771 # Security is enabled but no CAs are loaded. We can't establish trust
817 # Security is enabled but no CAs are loaded. We can't establish trust
772 # for the cert so abort.
818 # for the cert so abort.
773 if not sock._hgstate['caloaded']:
819 if not sock._hgstate['caloaded']:
774 raise error.Abort(
820 raise error.Abort(
775 _('unable to verify security of %s (no loaded CA certificates); '
821 _('unable to verify security of %s (no loaded CA certificates); '
776 'refusing to connect') % host,
822 'refusing to connect') % host,
777 hint=_('see https://mercurial-scm.org/wiki/SecureConnections for '
823 hint=_('see https://mercurial-scm.org/wiki/SecureConnections for '
778 'how to configure Mercurial to avoid this error or set '
824 'how to configure Mercurial to avoid this error or set '
779 'hostsecurity.%s:fingerprints=%s to trust this server') %
825 'hostsecurity.%s:fingerprints=%s to trust this server') %
780 (host, nicefingerprint))
826 (host, nicefingerprint))
781
827
782 msg = _verifycert(peercert2, host)
828 msg = _verifycert(peercert2, host)
783 if msg:
829 if msg:
784 raise error.Abort(_('%s certificate error: %s') % (host, msg),
830 raise error.Abort(_('%s certificate error: %s') % (host, msg),
785 hint=_('set hostsecurity.%s:certfingerprints=%s '
831 hint=_('set hostsecurity.%s:certfingerprints=%s '
786 'config setting or use --insecure to connect '
832 'config setting or use --insecure to connect '
787 'insecurely') %
833 'insecurely') %
788 (host, nicefingerprint))
834 (host, nicefingerprint))
@@ -1,625 +1,637 b''
1 #require serve ssl
1 #require serve ssl
2
2
3 Proper https client requires the built-in ssl from Python 2.6.
3 Proper https client requires the built-in ssl from Python 2.6.
4
4
5 Make server certificates:
5 Make server certificates:
6
6
7 $ CERTSDIR="$TESTDIR/sslcerts"
7 $ CERTSDIR="$TESTDIR/sslcerts"
8 $ cat "$CERTSDIR/priv.pem" "$CERTSDIR/pub.pem" >> server.pem
8 $ cat "$CERTSDIR/priv.pem" "$CERTSDIR/pub.pem" >> server.pem
9 $ PRIV=`pwd`/server.pem
9 $ PRIV=`pwd`/server.pem
10 $ cat "$CERTSDIR/priv.pem" "$CERTSDIR/pub-not-yet.pem" > server-not-yet.pem
10 $ cat "$CERTSDIR/priv.pem" "$CERTSDIR/pub-not-yet.pem" > server-not-yet.pem
11 $ cat "$CERTSDIR/priv.pem" "$CERTSDIR/pub-expired.pem" > server-expired.pem
11 $ cat "$CERTSDIR/priv.pem" "$CERTSDIR/pub-expired.pem" > server-expired.pem
12
12
13 $ hg init test
13 $ hg init test
14 $ cd test
14 $ cd test
15 $ echo foo>foo
15 $ echo foo>foo
16 $ mkdir foo.d foo.d/bAr.hg.d foo.d/baR.d.hg
16 $ mkdir foo.d foo.d/bAr.hg.d foo.d/baR.d.hg
17 $ echo foo>foo.d/foo
17 $ echo foo>foo.d/foo
18 $ echo bar>foo.d/bAr.hg.d/BaR
18 $ echo bar>foo.d/bAr.hg.d/BaR
19 $ echo bar>foo.d/baR.d.hg/bAR
19 $ echo bar>foo.d/baR.d.hg/bAR
20 $ hg commit -A -m 1
20 $ hg commit -A -m 1
21 adding foo
21 adding foo
22 adding foo.d/bAr.hg.d/BaR
22 adding foo.d/bAr.hg.d/BaR
23 adding foo.d/baR.d.hg/bAR
23 adding foo.d/baR.d.hg/bAR
24 adding foo.d/foo
24 adding foo.d/foo
25 $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV
25 $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV
26 $ cat ../hg0.pid >> $DAEMON_PIDS
26 $ cat ../hg0.pid >> $DAEMON_PIDS
27
27
28 cacert not found
28 cacert not found
29
29
30 $ hg in --config web.cacerts=no-such.pem https://localhost:$HGPORT/
30 $ hg in --config web.cacerts=no-such.pem https://localhost:$HGPORT/
31 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
31 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
32 abort: could not find web.cacerts: no-such.pem
32 abort: could not find web.cacerts: no-such.pem
33 [255]
33 [255]
34
34
35 Test server address cannot be reused
35 Test server address cannot be reused
36
36
37 #if windows
37 #if windows
38 $ hg serve -p $HGPORT --certificate=$PRIV 2>&1
38 $ hg serve -p $HGPORT --certificate=$PRIV 2>&1
39 abort: cannot start server at ':$HGPORT':
39 abort: cannot start server at ':$HGPORT':
40 [255]
40 [255]
41 #else
41 #else
42 $ hg serve -p $HGPORT --certificate=$PRIV 2>&1
42 $ hg serve -p $HGPORT --certificate=$PRIV 2>&1
43 abort: cannot start server at ':$HGPORT': Address already in use
43 abort: cannot start server at ':$HGPORT': Address already in use
44 [255]
44 [255]
45 #endif
45 #endif
46 $ cd ..
46 $ cd ..
47
47
48 Our test cert is not signed by a trusted CA. It should fail to verify if
48 Our test cert is not signed by a trusted CA. It should fail to verify if
49 we are able to load CA certs.
49 we are able to load CA certs.
50
50
51 #if sslcontext defaultcacerts no-defaultcacertsloaded
51 #if sslcontext defaultcacerts no-defaultcacertsloaded
52 $ hg clone https://localhost:$HGPORT/ copy-pull
52 $ hg clone https://localhost:$HGPORT/ copy-pull
53 (an attempt was made to load CA certificates but none were loaded; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error)
53 (an attempt was made to load CA certificates but none were loaded; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error)
54 abort: error: *certificate verify failed* (glob)
54 abort: error: *certificate verify failed* (glob)
55 [255]
55 [255]
56 #endif
56 #endif
57
57
58 #if no-sslcontext defaultcacerts
58 #if no-sslcontext defaultcacerts
59 $ hg clone https://localhost:$HGPORT/ copy-pull
59 $ hg clone https://localhost:$HGPORT/ copy-pull
60 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
60 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
61 (using CA certificates from *; if you see this message, your Mercurial install is not properly configured; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message) (glob) (?)
61 (using CA certificates from *; if you see this message, your Mercurial install is not properly configured; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message) (glob) (?)
62 abort: error: *certificate verify failed* (glob)
62 abort: error: *certificate verify failed* (glob)
63 [255]
63 [255]
64 #endif
64 #endif
65
65
66 #if no-sslcontext windows
66 #if no-sslcontext windows
67 $ hg clone https://localhost:$HGPORT/ copy-pull
67 $ hg clone https://localhost:$HGPORT/ copy-pull
68 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info
68 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info
69 (unable to load Windows CA certificates; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message)
69 (unable to load Windows CA certificates; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message)
70 abort: error: *certificate verify failed* (glob)
70 abort: error: *certificate verify failed* (glob)
71 [255]
71 [255]
72 #endif
72 #endif
73
73
74 #if no-sslcontext osx
74 #if no-sslcontext osx
75 $ hg clone https://localhost:$HGPORT/ copy-pull
75 $ hg clone https://localhost:$HGPORT/ copy-pull
76 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info
76 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info
77 (unable to load CA certificates; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message)
77 (unable to load CA certificates; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message)
78 abort: localhost certificate error: no certificate received
78 abort: localhost certificate error: no certificate received
79 (set hostsecurity.localhost:certfingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e config setting or use --insecure to connect insecurely)
79 (set hostsecurity.localhost:certfingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e config setting or use --insecure to connect insecurely)
80 [255]
80 [255]
81 #endif
81 #endif
82
82
83 #if defaultcacertsloaded
83 #if defaultcacertsloaded
84 $ hg clone https://localhost:$HGPORT/ copy-pull
84 $ hg clone https://localhost:$HGPORT/ copy-pull
85 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
85 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
86 (using CA certificates from *; if you see this message, your Mercurial install is not properly configured; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message) (glob) (?)
86 (using CA certificates from *; if you see this message, your Mercurial install is not properly configured; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message) (glob) (?)
87 abort: error: *certificate verify failed* (glob)
87 abort: error: *certificate verify failed* (glob)
88 [255]
88 [255]
89 #endif
89 #endif
90
90
91 #if no-defaultcacerts
91 #if no-defaultcacerts
92 $ hg clone https://localhost:$HGPORT/ copy-pull
92 $ hg clone https://localhost:$HGPORT/ copy-pull
93 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
93 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
94 (unable to load * certificates; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message) (glob) (?)
94 (unable to load * certificates; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message) (glob) (?)
95 abort: localhost certificate error: no certificate received
95 abort: localhost certificate error: no certificate received
96 (set hostsecurity.localhost:certfingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e config setting or use --insecure to connect insecurely)
96 (set hostsecurity.localhost:certfingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e config setting or use --insecure to connect insecurely)
97 [255]
97 [255]
98 #endif
98 #endif
99
99
100 Specifying a per-host certificate file that doesn't exist will abort
100 Specifying a per-host certificate file that doesn't exist will abort
101
101
102 $ hg --config hostsecurity.localhost:verifycertsfile=/does/not/exist clone https://localhost:$HGPORT/
102 $ hg --config hostsecurity.localhost:verifycertsfile=/does/not/exist clone https://localhost:$HGPORT/
103 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
103 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
104 abort: path specified by hostsecurity.localhost:verifycertsfile does not exist: /does/not/exist
104 abort: path specified by hostsecurity.localhost:verifycertsfile does not exist: /does/not/exist
105 [255]
105 [255]
106
106
107 A malformed per-host certificate file will raise an error
107 A malformed per-host certificate file will raise an error
108
108
109 $ echo baddata > badca.pem
109 $ echo baddata > badca.pem
110 #if sslcontext
110 #if sslcontext
111 $ hg --config hostsecurity.localhost:verifycertsfile=badca.pem clone https://localhost:$HGPORT/
111 $ hg --config hostsecurity.localhost:verifycertsfile=badca.pem clone https://localhost:$HGPORT/
112 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
112 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
113 abort: error loading CA file badca.pem: * (glob)
113 abort: error loading CA file badca.pem: * (glob)
114 (file is empty or malformed?)
114 (file is empty or malformed?)
115 [255]
115 [255]
116 #else
116 #else
117 $ hg --config hostsecurity.localhost:verifycertsfile=badca.pem clone https://localhost:$HGPORT/
117 $ hg --config hostsecurity.localhost:verifycertsfile=badca.pem clone https://localhost:$HGPORT/
118 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
118 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
119 abort: error: * (glob)
119 abort: error: * (glob)
120 [255]
120 [255]
121 #endif
121 #endif
122
122
123 A per-host certificate mismatching the server will fail verification
123 A per-host certificate mismatching the server will fail verification
124
124
125 (modern ssl is able to discern whether the loaded cert is a CA cert)
125 (modern ssl is able to discern whether the loaded cert is a CA cert)
126 #if sslcontext
126 #if sslcontext
127 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/client-cert.pem" clone https://localhost:$HGPORT/
127 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/client-cert.pem" clone https://localhost:$HGPORT/
128 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
128 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
129 (an attempt was made to load CA certificates but none were loaded; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error)
129 (an attempt was made to load CA certificates but none were loaded; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error)
130 abort: error: *certificate verify failed* (glob)
130 abort: error: *certificate verify failed* (glob)
131 [255]
131 [255]
132 #else
132 #else
133 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/client-cert.pem" clone https://localhost:$HGPORT/
133 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/client-cert.pem" clone https://localhost:$HGPORT/
134 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
134 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
135 abort: error: *certificate verify failed* (glob)
135 abort: error: *certificate verify failed* (glob)
136 [255]
136 [255]
137 #endif
137 #endif
138
138
139 A per-host certificate matching the server's cert will be accepted
139 A per-host certificate matching the server's cert will be accepted
140
140
141 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/pub.pem" clone -U https://localhost:$HGPORT/ perhostgood1
141 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/pub.pem" clone -U https://localhost:$HGPORT/ perhostgood1
142 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
142 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
143 requesting all changes
143 requesting all changes
144 adding changesets
144 adding changesets
145 adding manifests
145 adding manifests
146 adding file changes
146 adding file changes
147 added 1 changesets with 4 changes to 4 files
147 added 1 changesets with 4 changes to 4 files
148
148
149 A per-host certificate with multiple certs and one matching will be accepted
149 A per-host certificate with multiple certs and one matching will be accepted
150
150
151 $ cat "$CERTSDIR/client-cert.pem" "$CERTSDIR/pub.pem" > perhost.pem
151 $ cat "$CERTSDIR/client-cert.pem" "$CERTSDIR/pub.pem" > perhost.pem
152 $ hg --config hostsecurity.localhost:verifycertsfile=perhost.pem clone -U https://localhost:$HGPORT/ perhostgood2
152 $ hg --config hostsecurity.localhost:verifycertsfile=perhost.pem clone -U https://localhost:$HGPORT/ perhostgood2
153 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
153 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
154 requesting all changes
154 requesting all changes
155 adding changesets
155 adding changesets
156 adding manifests
156 adding manifests
157 adding file changes
157 adding file changes
158 added 1 changesets with 4 changes to 4 files
158 added 1 changesets with 4 changes to 4 files
159
159
160 Defining both per-host certificate and a fingerprint will print a warning
160 Defining both per-host certificate and a fingerprint will print a warning
161
161
162 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/pub.pem" --config hostsecurity.localhost:fingerprints=sha1:ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03 clone -U https://localhost:$HGPORT/ caandfingerwarning
162 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/pub.pem" --config hostsecurity.localhost:fingerprints=sha1:ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03 clone -U https://localhost:$HGPORT/ caandfingerwarning
163 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
163 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
164 (hostsecurity.localhost:verifycertsfile ignored when host fingerprints defined; using host fingerprints for verification)
164 (hostsecurity.localhost:verifycertsfile ignored when host fingerprints defined; using host fingerprints for verification)
165 requesting all changes
165 requesting all changes
166 adding changesets
166 adding changesets
167 adding manifests
167 adding manifests
168 adding file changes
168 adding file changes
169 added 1 changesets with 4 changes to 4 files
169 added 1 changesets with 4 changes to 4 files
170
170
171 $ DISABLECACERTS="--config devel.disableloaddefaultcerts=true"
171 $ DISABLECACERTS="--config devel.disableloaddefaultcerts=true"
172
172
173 Inability to verify peer certificate will result in abort
173 Inability to verify peer certificate will result in abort
174
174
175 $ hg clone https://localhost:$HGPORT/ copy-pull $DISABLECACERTS
175 $ hg clone https://localhost:$HGPORT/ copy-pull $DISABLECACERTS
176 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
176 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
177 abort: unable to verify security of localhost (no loaded CA certificates); refusing to connect
177 abort: unable to verify security of localhost (no loaded CA certificates); refusing to connect
178 (see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error or set hostsecurity.localhost:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e to trust this server)
178 (see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error or set hostsecurity.localhost:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e to trust this server)
179 [255]
179 [255]
180
180
181 $ hg clone --insecure https://localhost:$HGPORT/ copy-pull
181 $ hg clone --insecure https://localhost:$HGPORT/ copy-pull
182 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
182 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
183 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
183 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
184 requesting all changes
184 requesting all changes
185 adding changesets
185 adding changesets
186 adding manifests
186 adding manifests
187 adding file changes
187 adding file changes
188 added 1 changesets with 4 changes to 4 files
188 added 1 changesets with 4 changes to 4 files
189 updating to branch default
189 updating to branch default
190 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
190 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
191 $ hg verify -R copy-pull
191 $ hg verify -R copy-pull
192 checking changesets
192 checking changesets
193 checking manifests
193 checking manifests
194 crosschecking files in changesets and manifests
194 crosschecking files in changesets and manifests
195 checking files
195 checking files
196 4 files, 1 changesets, 4 total revisions
196 4 files, 1 changesets, 4 total revisions
197 $ cd test
197 $ cd test
198 $ echo bar > bar
198 $ echo bar > bar
199 $ hg commit -A -d '1 0' -m 2
199 $ hg commit -A -d '1 0' -m 2
200 adding bar
200 adding bar
201 $ cd ..
201 $ cd ..
202
202
203 pull without cacert
203 pull without cacert
204
204
205 $ cd copy-pull
205 $ cd copy-pull
206 $ echo '[hooks]' >> .hg/hgrc
206 $ echo '[hooks]' >> .hg/hgrc
207 $ echo "changegroup = printenv.py changegroup" >> .hg/hgrc
207 $ echo "changegroup = printenv.py changegroup" >> .hg/hgrc
208 $ hg pull $DISABLECACERTS
208 $ hg pull $DISABLECACERTS
209 pulling from https://localhost:$HGPORT/
209 pulling from https://localhost:$HGPORT/
210 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
210 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
211 abort: unable to verify security of localhost (no loaded CA certificates); refusing to connect
211 abort: unable to verify security of localhost (no loaded CA certificates); refusing to connect
212 (see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error or set hostsecurity.localhost:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e to trust this server)
212 (see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error or set hostsecurity.localhost:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e to trust this server)
213 [255]
213 [255]
214
214
215 $ hg pull --insecure
215 $ hg pull --insecure
216 pulling from https://localhost:$HGPORT/
216 pulling from https://localhost:$HGPORT/
217 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
217 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
218 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
218 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
219 searching for changes
219 searching for changes
220 adding changesets
220 adding changesets
221 adding manifests
221 adding manifests
222 adding file changes
222 adding file changes
223 added 1 changesets with 1 changes to 1 files
223 added 1 changesets with 1 changes to 1 files
224 changegroup hook: HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_NODE_LAST=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=https://localhost:$HGPORT/ (glob)
224 changegroup hook: HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_NODE_LAST=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=https://localhost:$HGPORT/ (glob)
225 (run 'hg update' to get a working copy)
225 (run 'hg update' to get a working copy)
226 $ cd ..
226 $ cd ..
227
227
228 cacert configured in local repo
228 cacert configured in local repo
229
229
230 $ cp copy-pull/.hg/hgrc copy-pull/.hg/hgrc.bu
230 $ cp copy-pull/.hg/hgrc copy-pull/.hg/hgrc.bu
231 $ echo "[web]" >> copy-pull/.hg/hgrc
231 $ echo "[web]" >> copy-pull/.hg/hgrc
232 $ echo "cacerts=$CERTSDIR/pub.pem" >> copy-pull/.hg/hgrc
232 $ echo "cacerts=$CERTSDIR/pub.pem" >> copy-pull/.hg/hgrc
233 $ hg -R copy-pull pull --traceback
233 $ hg -R copy-pull pull --traceback
234 pulling from https://localhost:$HGPORT/
234 pulling from https://localhost:$HGPORT/
235 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
235 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
236 searching for changes
236 searching for changes
237 no changes found
237 no changes found
238 $ mv copy-pull/.hg/hgrc.bu copy-pull/.hg/hgrc
238 $ mv copy-pull/.hg/hgrc.bu copy-pull/.hg/hgrc
239
239
240 cacert configured globally, also testing expansion of environment
240 cacert configured globally, also testing expansion of environment
241 variables in the filename
241 variables in the filename
242
242
243 $ echo "[web]" >> $HGRCPATH
243 $ echo "[web]" >> $HGRCPATH
244 $ echo 'cacerts=$P/pub.pem' >> $HGRCPATH
244 $ echo 'cacerts=$P/pub.pem' >> $HGRCPATH
245 $ P="$CERTSDIR" hg -R copy-pull pull
245 $ P="$CERTSDIR" hg -R copy-pull pull
246 pulling from https://localhost:$HGPORT/
246 pulling from https://localhost:$HGPORT/
247 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
247 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
248 searching for changes
248 searching for changes
249 no changes found
249 no changes found
250 $ P="$CERTSDIR" hg -R copy-pull pull --insecure
250 $ P="$CERTSDIR" hg -R copy-pull pull --insecure
251 pulling from https://localhost:$HGPORT/
251 pulling from https://localhost:$HGPORT/
252 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
252 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
253 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
253 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
254 searching for changes
254 searching for changes
255 no changes found
255 no changes found
256
256
257 empty cacert file
257 empty cacert file
258
258
259 $ touch emptycafile
259 $ touch emptycafile
260
260
261 #if sslcontext
261 #if sslcontext
262 $ hg --config web.cacerts=emptycafile -R copy-pull pull
262 $ hg --config web.cacerts=emptycafile -R copy-pull pull
263 pulling from https://localhost:$HGPORT/
263 pulling from https://localhost:$HGPORT/
264 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
264 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
265 abort: error loading CA file emptycafile: * (glob)
265 abort: error loading CA file emptycafile: * (glob)
266 (file is empty or malformed?)
266 (file is empty or malformed?)
267 [255]
267 [255]
268 #else
268 #else
269 $ hg --config web.cacerts=emptycafile -R copy-pull pull
269 $ hg --config web.cacerts=emptycafile -R copy-pull pull
270 pulling from https://localhost:$HGPORT/
270 pulling from https://localhost:$HGPORT/
271 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
271 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
272 abort: error: * (glob)
272 abort: error: * (glob)
273 [255]
273 [255]
274 #endif
274 #endif
275
275
276 cacert mismatch
276 cacert mismatch
277
277
278 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub.pem" \
278 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub.pem" \
279 > https://127.0.0.1:$HGPORT/
279 > https://127.0.0.1:$HGPORT/
280 pulling from https://127.0.0.1:$HGPORT/ (glob)
280 pulling from https://127.0.0.1:$HGPORT/ (glob)
281 warning: connecting to 127.0.0.1 using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
281 warning: connecting to 127.0.0.1 using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
282 abort: 127.0.0.1 certificate error: certificate is for localhost (glob)
282 abort: 127.0.0.1 certificate error: certificate is for localhost (glob)
283 (set hostsecurity.127.0.0.1:certfingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e config setting or use --insecure to connect insecurely) (glob)
283 (set hostsecurity.127.0.0.1:certfingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e config setting or use --insecure to connect insecurely) (glob)
284 [255]
284 [255]
285 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub.pem" \
285 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub.pem" \
286 > https://127.0.0.1:$HGPORT/ --insecure
286 > https://127.0.0.1:$HGPORT/ --insecure
287 pulling from https://127.0.0.1:$HGPORT/ (glob)
287 pulling from https://127.0.0.1:$HGPORT/ (glob)
288 warning: connecting to 127.0.0.1 using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
288 warning: connecting to 127.0.0.1 using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
289 warning: connection security to 127.0.0.1 is disabled per current settings; communication is susceptible to eavesdropping and tampering (glob)
289 warning: connection security to 127.0.0.1 is disabled per current settings; communication is susceptible to eavesdropping and tampering (glob)
290 searching for changes
290 searching for changes
291 no changes found
291 no changes found
292 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-other.pem"
292 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-other.pem"
293 pulling from https://localhost:$HGPORT/
293 pulling from https://localhost:$HGPORT/
294 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
294 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
295 abort: error: *certificate verify failed* (glob)
295 abort: error: *certificate verify failed* (glob)
296 [255]
296 [255]
297 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-other.pem" \
297 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-other.pem" \
298 > --insecure
298 > --insecure
299 pulling from https://localhost:$HGPORT/
299 pulling from https://localhost:$HGPORT/
300 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
300 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
301 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
301 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
302 searching for changes
302 searching for changes
303 no changes found
303 no changes found
304
304
305 Test server cert which isn't valid yet
305 Test server cert which isn't valid yet
306
306
307 $ hg serve -R test -p $HGPORT1 -d --pid-file=hg1.pid --certificate=server-not-yet.pem
307 $ hg serve -R test -p $HGPORT1 -d --pid-file=hg1.pid --certificate=server-not-yet.pem
308 $ cat hg1.pid >> $DAEMON_PIDS
308 $ cat hg1.pid >> $DAEMON_PIDS
309 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-not-yet.pem" \
309 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-not-yet.pem" \
310 > https://localhost:$HGPORT1/
310 > https://localhost:$HGPORT1/
311 pulling from https://localhost:$HGPORT1/
311 pulling from https://localhost:$HGPORT1/
312 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
312 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
313 abort: error: *certificate verify failed* (glob)
313 abort: error: *certificate verify failed* (glob)
314 [255]
314 [255]
315
315
316 Test server cert which no longer is valid
316 Test server cert which no longer is valid
317
317
318 $ hg serve -R test -p $HGPORT2 -d --pid-file=hg2.pid --certificate=server-expired.pem
318 $ hg serve -R test -p $HGPORT2 -d --pid-file=hg2.pid --certificate=server-expired.pem
319 $ cat hg2.pid >> $DAEMON_PIDS
319 $ cat hg2.pid >> $DAEMON_PIDS
320 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-expired.pem" \
320 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-expired.pem" \
321 > https://localhost:$HGPORT2/
321 > https://localhost:$HGPORT2/
322 pulling from https://localhost:$HGPORT2/
322 pulling from https://localhost:$HGPORT2/
323 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
323 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
324 abort: error: *certificate verify failed* (glob)
324 abort: error: *certificate verify failed* (glob)
325 [255]
325 [255]
326
326
327 Disabling the TLS 1.0 warning works
327 Disabling the TLS 1.0 warning works
328 $ hg -R copy-pull id https://localhost:$HGPORT/ \
328 $ hg -R copy-pull id https://localhost:$HGPORT/ \
329 > --config hostsecurity.localhost:fingerprints=sha1:ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03 \
329 > --config hostsecurity.localhost:fingerprints=sha1:ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03 \
330 > --config hostsecurity.disabletls10warning=true
330 > --config hostsecurity.disabletls10warning=true
331 5fed3813f7f5
331 5fed3813f7f5
332
332
333 #if no-sslcontext no-py27+
333 #if no-sslcontext no-py27+
334 Setting ciphers doesn't work in Python 2.6
334 Setting ciphers doesn't work in Python 2.6
335 $ P="$CERTSDIR" hg --config hostsecurity.ciphers=HIGH -R copy-pull id https://localhost:$HGPORT/
335 $ P="$CERTSDIR" hg --config hostsecurity.ciphers=HIGH -R copy-pull id https://localhost:$HGPORT/
336 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info
336 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info
337 abort: setting ciphers in [hostsecurity] is not supported by this version of Python
337 abort: setting ciphers in [hostsecurity] is not supported by this version of Python
338 (remove the config option or run Mercurial with a modern Python version (preferred))
338 (remove the config option or run Mercurial with a modern Python version (preferred))
339 [255]
339 [255]
340 #endif
340 #endif
341
341
342 Setting ciphers works in Python 2.7+ but the error message is different on
342 Setting ciphers works in Python 2.7+ but the error message is different on
343 legacy ssl. We test legacy once and do more feature checking on modern
343 legacy ssl. We test legacy once and do more feature checking on modern
344 configs.
344 configs.
345
345
346 #if py27+ no-sslcontext
346 #if py27+ no-sslcontext
347 $ P="$CERTSDIR" hg --config hostsecurity.ciphers=invalid -R copy-pull id https://localhost:$HGPORT/
347 $ P="$CERTSDIR" hg --config hostsecurity.ciphers=invalid -R copy-pull id https://localhost:$HGPORT/
348 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info
348 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info
349 abort: *No cipher can be selected. (glob)
349 abort: *No cipher can be selected. (glob)
350 [255]
350 [255]
351
351
352 $ P="$CERTSDIR" hg --config hostsecurity.ciphers=HIGH -R copy-pull id https://localhost:$HGPORT/
352 $ P="$CERTSDIR" hg --config hostsecurity.ciphers=HIGH -R copy-pull id https://localhost:$HGPORT/
353 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info
353 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info
354 5fed3813f7f5
354 5fed3813f7f5
355 #endif
355 #endif
356
356
357 #if sslcontext
357 #if sslcontext
358 Setting ciphers to an invalid value aborts
358 Setting ciphers to an invalid value aborts
359 $ P="$CERTSDIR" hg --config hostsecurity.ciphers=invalid -R copy-pull id https://localhost:$HGPORT/
359 $ P="$CERTSDIR" hg --config hostsecurity.ciphers=invalid -R copy-pull id https://localhost:$HGPORT/
360 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
360 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
361 abort: could not set ciphers: No cipher can be selected.
361 abort: could not set ciphers: No cipher can be selected.
362 (change cipher string (invalid) in config)
362 (change cipher string (invalid) in config)
363 [255]
363 [255]
364
364
365 $ P="$CERTSDIR" hg --config hostsecurity.localhost:ciphers=invalid -R copy-pull id https://localhost:$HGPORT/
365 $ P="$CERTSDIR" hg --config hostsecurity.localhost:ciphers=invalid -R copy-pull id https://localhost:$HGPORT/
366 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
366 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
367 abort: could not set ciphers: No cipher can be selected.
367 abort: could not set ciphers: No cipher can be selected.
368 (change cipher string (invalid) in config)
368 (change cipher string (invalid) in config)
369 [255]
369 [255]
370
370
371 Changing the cipher string works
371 Changing the cipher string works
372
372
373 $ P="$CERTSDIR" hg --config hostsecurity.ciphers=HIGH -R copy-pull id https://localhost:$HGPORT/
373 $ P="$CERTSDIR" hg --config hostsecurity.ciphers=HIGH -R copy-pull id https://localhost:$HGPORT/
374 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
374 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
375 5fed3813f7f5
375 5fed3813f7f5
376 #endif
376 #endif
377
377
378 Fingerprints
378 Fingerprints
379
379
380 - works without cacerts (hostkeyfingerprints)
380 - works without cacerts (hostkeyfingerprints)
381 $ 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
381 $ 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
382 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
382 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
383 5fed3813f7f5
383 5fed3813f7f5
384
384
385 - works without cacerts (hostsecurity)
385 - works without cacerts (hostsecurity)
386 $ hg -R copy-pull id https://localhost:$HGPORT/ --config hostsecurity.localhost:fingerprints=sha1:ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03
386 $ hg -R copy-pull id https://localhost:$HGPORT/ --config hostsecurity.localhost:fingerprints=sha1:ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03
387 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
387 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
388 5fed3813f7f5
388 5fed3813f7f5
389
389
390 $ hg -R copy-pull id https://localhost:$HGPORT/ --config hostsecurity.localhost:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e
390 $ hg -R copy-pull id https://localhost:$HGPORT/ --config hostsecurity.localhost:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e
391 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
391 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
392 5fed3813f7f5
392 5fed3813f7f5
393
393
394 - multiple fingerprints specified and first matches
394 - multiple fingerprints specified and first matches
395 $ hg --config 'hostfingerprints.localhost=ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03, deadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/ --insecure
395 $ hg --config 'hostfingerprints.localhost=ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03, deadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/ --insecure
396 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
396 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
397 5fed3813f7f5
397 5fed3813f7f5
398
398
399 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03, sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/
399 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03, sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/
400 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
400 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
401 5fed3813f7f5
401 5fed3813f7f5
402
402
403 - multiple fingerprints specified and last matches
403 - multiple fingerprints specified and last matches
404 $ hg --config 'hostfingerprints.localhost=deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03' -R copy-pull id https://localhost:$HGPORT/ --insecure
404 $ hg --config 'hostfingerprints.localhost=deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03' -R copy-pull id https://localhost:$HGPORT/ --insecure
405 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
405 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
406 5fed3813f7f5
406 5fed3813f7f5
407
407
408 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, sha1:ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03' -R copy-pull id https://localhost:$HGPORT/
408 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, sha1:ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03' -R copy-pull id https://localhost:$HGPORT/
409 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
409 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
410 5fed3813f7f5
410 5fed3813f7f5
411
411
412 - multiple fingerprints specified and none match
412 - multiple fingerprints specified and none match
413
413
414 $ hg --config 'hostfingerprints.localhost=deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, aeadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/ --insecure
414 $ hg --config 'hostfingerprints.localhost=deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, aeadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/ --insecure
415 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
415 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
416 abort: certificate for localhost has unexpected fingerprint ec:d8:7c:d6:b3:86:d0:4f:c1:b8:b4:1c:9d:8f:5e:16:8e:ef:1c:03
416 abort: certificate for localhost has unexpected fingerprint ec:d8:7c:d6:b3:86:d0:4f:c1:b8:b4:1c:9d:8f:5e:16:8e:ef:1c:03
417 (check hostfingerprint configuration)
417 (check hostfingerprint configuration)
418 [255]
418 [255]
419
419
420 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, sha1:aeadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/
420 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, sha1:aeadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/
421 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
421 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
422 abort: certificate for localhost has unexpected fingerprint sha1:ec:d8:7c:d6:b3:86:d0:4f:c1:b8:b4:1c:9d:8f:5e:16:8e:ef:1c:03
422 abort: certificate for localhost has unexpected fingerprint sha1:ec:d8:7c:d6:b3:86:d0:4f:c1:b8:b4:1c:9d:8f:5e:16:8e:ef:1c:03
423 (check hostsecurity configuration)
423 (check hostsecurity configuration)
424 [255]
424 [255]
425
425
426 - fails when cert doesn't match hostname (port is ignored)
426 - fails when cert doesn't match hostname (port is ignored)
427 $ hg -R copy-pull id https://localhost:$HGPORT1/ --config hostfingerprints.localhost=ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03
427 $ hg -R copy-pull id https://localhost:$HGPORT1/ --config hostfingerprints.localhost=ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03
428 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
428 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
429 abort: certificate for localhost has unexpected fingerprint f4:2f:5a:0c:3e:52:5b:db:e7:24:a8:32:1d:18:97:6d:69:b5:87:84
429 abort: certificate for localhost has unexpected fingerprint f4:2f:5a:0c:3e:52:5b:db:e7:24:a8:32:1d:18:97:6d:69:b5:87:84
430 (check hostfingerprint configuration)
430 (check hostfingerprint configuration)
431 [255]
431 [255]
432
432
433
433
434 - ignores that certificate doesn't match hostname
434 - ignores that certificate doesn't match hostname
435 $ hg -R copy-pull id https://127.0.0.1:$HGPORT/ --config hostfingerprints.127.0.0.1=ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03
435 $ hg -R copy-pull id https://127.0.0.1:$HGPORT/ --config hostfingerprints.127.0.0.1=ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03
436 warning: connecting to 127.0.0.1 using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
436 warning: connecting to 127.0.0.1 using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
437 5fed3813f7f5
437 5fed3813f7f5
438
438
439 Ports used by next test. Kill servers.
439 Ports used by next test. Kill servers.
440
440
441 $ killdaemons.py hg0.pid
441 $ killdaemons.py hg0.pid
442 $ killdaemons.py hg1.pid
442 $ killdaemons.py hg1.pid
443 $ killdaemons.py hg2.pid
443 $ killdaemons.py hg2.pid
444
444
445 #if sslcontext tls1.2
445 #if sslcontext tls1.2
446 Start servers running supported TLS versions
446 Start servers running supported TLS versions
447
447
448 $ cd test
448 $ cd test
449 $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV \
449 $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV \
450 > --config devel.serverexactprotocol=tls1.0
450 > --config devel.serverexactprotocol=tls1.0
451 $ cat ../hg0.pid >> $DAEMON_PIDS
451 $ cat ../hg0.pid >> $DAEMON_PIDS
452 $ hg serve -p $HGPORT1 -d --pid-file=../hg1.pid --certificate=$PRIV \
452 $ hg serve -p $HGPORT1 -d --pid-file=../hg1.pid --certificate=$PRIV \
453 > --config devel.serverexactprotocol=tls1.1
453 > --config devel.serverexactprotocol=tls1.1
454 $ cat ../hg1.pid >> $DAEMON_PIDS
454 $ cat ../hg1.pid >> $DAEMON_PIDS
455 $ hg serve -p $HGPORT2 -d --pid-file=../hg2.pid --certificate=$PRIV \
455 $ hg serve -p $HGPORT2 -d --pid-file=../hg2.pid --certificate=$PRIV \
456 > --config devel.serverexactprotocol=tls1.2
456 > --config devel.serverexactprotocol=tls1.2
457 $ cat ../hg2.pid >> $DAEMON_PIDS
457 $ cat ../hg2.pid >> $DAEMON_PIDS
458 $ cd ..
458 $ cd ..
459
459
460 Clients talking same TLS versions work
460 Clients talking same TLS versions work
461
461
462 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.0 id https://localhost:$HGPORT/
462 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.0 id https://localhost:$HGPORT/
463 5fed3813f7f5
463 5fed3813f7f5
464 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.1 id https://localhost:$HGPORT1/
464 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.1 id https://localhost:$HGPORT1/
465 5fed3813f7f5
465 5fed3813f7f5
466 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.2 id https://localhost:$HGPORT2/
466 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.2 id https://localhost:$HGPORT2/
467 5fed3813f7f5
467 5fed3813f7f5
468
468
469 Clients requiring newer TLS version than what server supports fail
469 Clients requiring newer TLS version than what server supports fail
470
470
471 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/
471 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/
472 (could not negotiate a common protocol; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error)
472 (could not negotiate a common security protocol (tls1.1+) with localhost; the likely cause is Mercurial is configured to be more secure than the server can support)
473 (consider contacting the operator of this server and ask them to support modern TLS protocol versions; or, set hostsecurity.localhost:minimumprotocol=tls1.0 to allow use of legacy, less secure protocols when communicating with this server)
474 (see https://mercurial-scm.org/wiki/SecureConnections for more info)
473 abort: error: *unsupported protocol* (glob)
475 abort: error: *unsupported protocol* (glob)
474 [255]
476 [255]
475
477
476 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.1 id https://localhost:$HGPORT/
478 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.1 id https://localhost:$HGPORT/
477 (could not negotiate a common protocol; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error)
479 (could not negotiate a common security protocol (tls1.1+) with localhost; the likely cause is Mercurial is configured to be more secure than the server can support)
480 (consider contacting the operator of this server and ask them to support modern TLS protocol versions; or, set hostsecurity.localhost:minimumprotocol=tls1.0 to allow use of legacy, less secure protocols when communicating with this server)
481 (see https://mercurial-scm.org/wiki/SecureConnections for more info)
478 abort: error: *unsupported protocol* (glob)
482 abort: error: *unsupported protocol* (glob)
479 [255]
483 [255]
480 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.2 id https://localhost:$HGPORT/
484 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.2 id https://localhost:$HGPORT/
481 (could not negotiate a common protocol; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error)
485 (could not negotiate a common security protocol (tls1.2+) with localhost; the likely cause is Mercurial is configured to be more secure than the server can support)
486 (consider contacting the operator of this server and ask them to support modern TLS protocol versions; or, set hostsecurity.localhost:minimumprotocol=tls1.0 to allow use of legacy, less secure protocols when communicating with this server)
487 (see https://mercurial-scm.org/wiki/SecureConnections for more info)
482 abort: error: *unsupported protocol* (glob)
488 abort: error: *unsupported protocol* (glob)
483 [255]
489 [255]
484 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.2 id https://localhost:$HGPORT1/
490 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.2 id https://localhost:$HGPORT1/
485 (could not negotiate a common protocol; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error)
491 (could not negotiate a common security protocol (tls1.2+) with localhost; the likely cause is Mercurial is configured to be more secure than the server can support)
492 (consider contacting the operator of this server and ask them to support modern TLS protocol versions; or, set hostsecurity.localhost:minimumprotocol=tls1.0 to allow use of legacy, less secure protocols when communicating with this server)
493 (see https://mercurial-scm.org/wiki/SecureConnections for more info)
486 abort: error: *unsupported protocol* (glob)
494 abort: error: *unsupported protocol* (glob)
487 [255]
495 [255]
488
496
489 --insecure will allow TLS 1.0 connections and override configs
497 --insecure will allow TLS 1.0 connections and override configs
490
498
491 $ hg --config hostsecurity.minimumprotocol=tls1.2 id --insecure https://localhost:$HGPORT1/
499 $ hg --config hostsecurity.minimumprotocol=tls1.2 id --insecure https://localhost:$HGPORT1/
492 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
500 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
493 5fed3813f7f5
501 5fed3813f7f5
494
502
495 The per-host config option overrides the default
503 The per-host config option overrides the default
496
504
497 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
505 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
498 > --config hostsecurity.minimumprotocol=tls1.2 \
506 > --config hostsecurity.minimumprotocol=tls1.2 \
499 > --config hostsecurity.localhost:minimumprotocol=tls1.0
507 > --config hostsecurity.localhost:minimumprotocol=tls1.0
500 5fed3813f7f5
508 5fed3813f7f5
501
509
502 The per-host config option by itself works
510 The per-host config option by itself works
503
511
504 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
512 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
505 > --config hostsecurity.localhost:minimumprotocol=tls1.2
513 > --config hostsecurity.localhost:minimumprotocol=tls1.2
506 (could not negotiate a common protocol; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error)
514 (could not negotiate a common security protocol (tls1.2+) with localhost; the likely cause is Mercurial is configured to be more secure than the server can support)
515 (consider contacting the operator of this server and ask them to support modern TLS protocol versions; or, set hostsecurity.localhost:minimumprotocol=tls1.0 to allow use of legacy, less secure protocols when communicating with this server)
516 (see https://mercurial-scm.org/wiki/SecureConnections for more info)
507 abort: error: *unsupported protocol* (glob)
517 abort: error: *unsupported protocol* (glob)
508 [255]
518 [255]
509
519
510 .hg/hgrc file [hostsecurity] settings are applied to remote ui instances (issue5305)
520 .hg/hgrc file [hostsecurity] settings are applied to remote ui instances (issue5305)
511
521
512 $ cat >> copy-pull/.hg/hgrc << EOF
522 $ cat >> copy-pull/.hg/hgrc << EOF
513 > [hostsecurity]
523 > [hostsecurity]
514 > localhost:minimumprotocol=tls1.2
524 > localhost:minimumprotocol=tls1.2
515 > EOF
525 > EOF
516 $ P="$CERTSDIR" hg -R copy-pull id https://localhost:$HGPORT/
526 $ P="$CERTSDIR" hg -R copy-pull id https://localhost:$HGPORT/
517 (could not negotiate a common protocol; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error)
527 (could not negotiate a common security protocol (tls1.2+) with localhost; the likely cause is Mercurial is configured to be more secure than the server can support)
528 (consider contacting the operator of this server and ask them to support modern TLS protocol versions; or, set hostsecurity.localhost:minimumprotocol=tls1.0 to allow use of legacy, less secure protocols when communicating with this server)
529 (see https://mercurial-scm.org/wiki/SecureConnections for more info)
518 abort: error: [SSL: UNSUPPORTED_PROTOCOL] unsupported protocol (_ssl.c:590)
530 abort: error: [SSL: UNSUPPORTED_PROTOCOL] unsupported protocol (_ssl.c:590)
519 [255]
531 [255]
520
532
521 $ killdaemons.py hg0.pid
533 $ killdaemons.py hg0.pid
522 $ killdaemons.py hg1.pid
534 $ killdaemons.py hg1.pid
523 $ killdaemons.py hg2.pid
535 $ killdaemons.py hg2.pid
524 #endif
536 #endif
525
537
526 Prepare for connecting through proxy
538 Prepare for connecting through proxy
527
539
528 $ hg serve -R test -p $HGPORT -d --pid-file=hg0.pid --certificate=$PRIV
540 $ hg serve -R test -p $HGPORT -d --pid-file=hg0.pid --certificate=$PRIV
529 $ cat hg0.pid >> $DAEMON_PIDS
541 $ cat hg0.pid >> $DAEMON_PIDS
530 $ hg serve -R test -p $HGPORT2 -d --pid-file=hg2.pid --certificate=server-expired.pem
542 $ hg serve -R test -p $HGPORT2 -d --pid-file=hg2.pid --certificate=server-expired.pem
531 $ cat hg2.pid >> $DAEMON_PIDS
543 $ cat hg2.pid >> $DAEMON_PIDS
532 tinyproxy.py doesn't fully detach, so killing it may result in extra output
544 tinyproxy.py doesn't fully detach, so killing it may result in extra output
533 from the shell. So don't kill it.
545 from the shell. So don't kill it.
534 $ tinyproxy.py $HGPORT1 localhost >proxy.log </dev/null 2>&1 &
546 $ tinyproxy.py $HGPORT1 localhost >proxy.log </dev/null 2>&1 &
535 $ while [ ! -f proxy.pid ]; do sleep 0; done
547 $ while [ ! -f proxy.pid ]; do sleep 0; done
536 $ cat proxy.pid >> $DAEMON_PIDS
548 $ cat proxy.pid >> $DAEMON_PIDS
537
549
538 $ echo "[http_proxy]" >> copy-pull/.hg/hgrc
550 $ echo "[http_proxy]" >> copy-pull/.hg/hgrc
539 $ echo "always=True" >> copy-pull/.hg/hgrc
551 $ echo "always=True" >> copy-pull/.hg/hgrc
540 $ echo "[hostfingerprints]" >> copy-pull/.hg/hgrc
552 $ echo "[hostfingerprints]" >> copy-pull/.hg/hgrc
541 $ echo "localhost =" >> copy-pull/.hg/hgrc
553 $ echo "localhost =" >> copy-pull/.hg/hgrc
542
554
543 Test unvalidated https through proxy
555 Test unvalidated https through proxy
544
556
545 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --insecure --traceback
557 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --insecure --traceback
546 pulling from https://localhost:$HGPORT/
558 pulling from https://localhost:$HGPORT/
547 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
559 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
548 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
560 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
549 searching for changes
561 searching for changes
550 no changes found
562 no changes found
551
563
552 Test https with cacert and fingerprint through proxy
564 Test https with cacert and fingerprint through proxy
553
565
554 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull \
566 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull \
555 > --config web.cacerts="$CERTSDIR/pub.pem"
567 > --config web.cacerts="$CERTSDIR/pub.pem"
556 pulling from https://localhost:$HGPORT/
568 pulling from https://localhost:$HGPORT/
557 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
569 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
558 searching for changes
570 searching for changes
559 no changes found
571 no changes found
560 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull https://127.0.0.1:$HGPORT/ --config hostfingerprints.127.0.0.1=ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03
572 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull https://127.0.0.1:$HGPORT/ --config hostfingerprints.127.0.0.1=ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03
561 pulling from https://127.0.0.1:$HGPORT/ (glob)
573 pulling from https://127.0.0.1:$HGPORT/ (glob)
562 warning: connecting to 127.0.0.1 using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
574 warning: connecting to 127.0.0.1 using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
563 searching for changes
575 searching for changes
564 no changes found
576 no changes found
565
577
566 Test https with cert problems through proxy
578 Test https with cert problems through proxy
567
579
568 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull \
580 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull \
569 > --config web.cacerts="$CERTSDIR/pub-other.pem"
581 > --config web.cacerts="$CERTSDIR/pub-other.pem"
570 pulling from https://localhost:$HGPORT/
582 pulling from https://localhost:$HGPORT/
571 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
583 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
572 abort: error: *certificate verify failed* (glob)
584 abort: error: *certificate verify failed* (glob)
573 [255]
585 [255]
574 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull \
586 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull \
575 > --config web.cacerts="$CERTSDIR/pub-expired.pem" https://localhost:$HGPORT2/
587 > --config web.cacerts="$CERTSDIR/pub-expired.pem" https://localhost:$HGPORT2/
576 pulling from https://localhost:$HGPORT2/
588 pulling from https://localhost:$HGPORT2/
577 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
589 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
578 abort: error: *certificate verify failed* (glob)
590 abort: error: *certificate verify failed* (glob)
579 [255]
591 [255]
580
592
581
593
582 $ killdaemons.py hg0.pid
594 $ killdaemons.py hg0.pid
583
595
584 #if sslcontext
596 #if sslcontext
585
597
586 Start hgweb that requires client certificates:
598 Start hgweb that requires client certificates:
587
599
588 $ cd test
600 $ cd test
589 $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV \
601 $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV \
590 > --config devel.servercafile=$PRIV --config devel.serverrequirecert=true
602 > --config devel.servercafile=$PRIV --config devel.serverrequirecert=true
591 $ cat ../hg0.pid >> $DAEMON_PIDS
603 $ cat ../hg0.pid >> $DAEMON_PIDS
592 $ cd ..
604 $ cd ..
593
605
594 without client certificate:
606 without client certificate:
595
607
596 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/
608 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/
597 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
609 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
598 abort: error: *handshake failure* (glob)
610 abort: error: *handshake failure* (glob)
599 [255]
611 [255]
600
612
601 with client certificate:
613 with client certificate:
602
614
603 $ cat << EOT >> $HGRCPATH
615 $ cat << EOT >> $HGRCPATH
604 > [auth]
616 > [auth]
605 > l.prefix = localhost
617 > l.prefix = localhost
606 > l.cert = $CERTSDIR/client-cert.pem
618 > l.cert = $CERTSDIR/client-cert.pem
607 > l.key = $CERTSDIR/client-key.pem
619 > l.key = $CERTSDIR/client-key.pem
608 > EOT
620 > EOT
609
621
610 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
622 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
611 > --config auth.l.key="$CERTSDIR/client-key-decrypted.pem"
623 > --config auth.l.key="$CERTSDIR/client-key-decrypted.pem"
612 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
624 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
613 5fed3813f7f5
625 5fed3813f7f5
614
626
615 $ printf '1234\n' | env P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
627 $ printf '1234\n' | env P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
616 > --config ui.interactive=True --config ui.nontty=True
628 > --config ui.interactive=True --config ui.nontty=True
617 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
629 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
618 passphrase for */client-key.pem: 5fed3813f7f5 (glob)
630 passphrase for */client-key.pem: 5fed3813f7f5 (glob)
619
631
620 $ env P="$CERTSDIR" hg id https://localhost:$HGPORT/
632 $ env P="$CERTSDIR" hg id https://localhost:$HGPORT/
621 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
633 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
622 abort: error: * (glob)
634 abort: error: * (glob)
623 [255]
635 [255]
624
636
625 #endif
637 #endif
General Comments 0
You need to be logged in to leave comments. Login now