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