##// END OF EJS Templates
sslutil: try to find CA certficates in well-known locations...
Gregory Szorc -
r29500:4b16a5bd default
parent child Browse files
Show More
@@ -1,564 +1,596
1 # sslutil.py - SSL handling for mercurial
1 # sslutil.py - SSL handling for mercurial
2 #
2 #
3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 from __future__ import absolute_import
10 from __future__ import absolute_import
11
11
12 import hashlib
12 import hashlib
13 import os
13 import os
14 import re
14 import re
15 import ssl
15 import ssl
16 import sys
16 import sys
17
17
18 from .i18n import _
18 from .i18n import _
19 from . import (
19 from . import (
20 error,
20 error,
21 util,
21 util,
22 )
22 )
23
23
24 # Python 2.7.9+ overhauled the built-in SSL/TLS features of Python. It added
24 # Python 2.7.9+ overhauled the built-in SSL/TLS features of Python. It added
25 # support for TLS 1.1, TLS 1.2, SNI, system CA stores, etc. These features are
25 # support for TLS 1.1, TLS 1.2, SNI, system CA stores, etc. These features are
26 # all exposed via the "ssl" module.
26 # all exposed via the "ssl" module.
27 #
27 #
28 # Depending on the version of Python being used, SSL/TLS support is either
28 # Depending on the version of Python being used, SSL/TLS support is either
29 # modern/secure or legacy/insecure. Many operations in this module have
29 # modern/secure or legacy/insecure. Many operations in this module have
30 # separate code paths depending on support in Python.
30 # separate code paths depending on support in Python.
31
31
32 hassni = getattr(ssl, 'HAS_SNI', False)
32 hassni = getattr(ssl, 'HAS_SNI', False)
33
33
34 try:
34 try:
35 OP_NO_SSLv2 = ssl.OP_NO_SSLv2
35 OP_NO_SSLv2 = ssl.OP_NO_SSLv2
36 OP_NO_SSLv3 = ssl.OP_NO_SSLv3
36 OP_NO_SSLv3 = ssl.OP_NO_SSLv3
37 except AttributeError:
37 except AttributeError:
38 OP_NO_SSLv2 = 0x1000000
38 OP_NO_SSLv2 = 0x1000000
39 OP_NO_SSLv3 = 0x2000000
39 OP_NO_SSLv3 = 0x2000000
40
40
41 try:
41 try:
42 # ssl.SSLContext was added in 2.7.9 and presence indicates modern
42 # ssl.SSLContext was added in 2.7.9 and presence indicates modern
43 # SSL/TLS features are available.
43 # SSL/TLS features are available.
44 SSLContext = ssl.SSLContext
44 SSLContext = ssl.SSLContext
45 modernssl = True
45 modernssl = True
46 _canloaddefaultcerts = util.safehasattr(SSLContext, 'load_default_certs')
46 _canloaddefaultcerts = util.safehasattr(SSLContext, 'load_default_certs')
47 except AttributeError:
47 except AttributeError:
48 modernssl = False
48 modernssl = False
49 _canloaddefaultcerts = False
49 _canloaddefaultcerts = False
50
50
51 # We implement SSLContext using the interface from the standard library.
51 # We implement SSLContext using the interface from the standard library.
52 class SSLContext(object):
52 class SSLContext(object):
53 # ssl.wrap_socket gained the "ciphers" named argument in 2.7.
53 # ssl.wrap_socket gained the "ciphers" named argument in 2.7.
54 _supportsciphers = sys.version_info >= (2, 7)
54 _supportsciphers = sys.version_info >= (2, 7)
55
55
56 def __init__(self, protocol):
56 def __init__(self, protocol):
57 # From the public interface of SSLContext
57 # From the public interface of SSLContext
58 self.protocol = protocol
58 self.protocol = protocol
59 self.check_hostname = False
59 self.check_hostname = False
60 self.options = 0
60 self.options = 0
61 self.verify_mode = ssl.CERT_NONE
61 self.verify_mode = ssl.CERT_NONE
62
62
63 # Used by our implementation.
63 # Used by our implementation.
64 self._certfile = None
64 self._certfile = None
65 self._keyfile = None
65 self._keyfile = None
66 self._certpassword = None
66 self._certpassword = None
67 self._cacerts = None
67 self._cacerts = None
68 self._ciphers = None
68 self._ciphers = None
69
69
70 def load_cert_chain(self, certfile, keyfile=None, password=None):
70 def load_cert_chain(self, certfile, keyfile=None, password=None):
71 self._certfile = certfile
71 self._certfile = certfile
72 self._keyfile = keyfile
72 self._keyfile = keyfile
73 self._certpassword = password
73 self._certpassword = password
74
74
75 def load_default_certs(self, purpose=None):
75 def load_default_certs(self, purpose=None):
76 pass
76 pass
77
77
78 def load_verify_locations(self, cafile=None, capath=None, cadata=None):
78 def load_verify_locations(self, cafile=None, capath=None, cadata=None):
79 if capath:
79 if capath:
80 raise error.Abort(_('capath not supported'))
80 raise error.Abort(_('capath not supported'))
81 if cadata:
81 if cadata:
82 raise error.Abort(_('cadata not supported'))
82 raise error.Abort(_('cadata not supported'))
83
83
84 self._cacerts = cafile
84 self._cacerts = cafile
85
85
86 def set_ciphers(self, ciphers):
86 def set_ciphers(self, ciphers):
87 if not self._supportsciphers:
87 if not self._supportsciphers:
88 raise error.Abort(_('setting ciphers not supported'))
88 raise error.Abort(_('setting ciphers not supported'))
89
89
90 self._ciphers = ciphers
90 self._ciphers = ciphers
91
91
92 def wrap_socket(self, socket, server_hostname=None, server_side=False):
92 def wrap_socket(self, socket, server_hostname=None, server_side=False):
93 # server_hostname is unique to SSLContext.wrap_socket and is used
93 # server_hostname is unique to SSLContext.wrap_socket and is used
94 # for SNI in that context. So there's nothing for us to do with it
94 # for SNI in that context. So there's nothing for us to do with it
95 # in this legacy code since we don't support SNI.
95 # in this legacy code since we don't support SNI.
96
96
97 args = {
97 args = {
98 'keyfile': self._keyfile,
98 'keyfile': self._keyfile,
99 'certfile': self._certfile,
99 'certfile': self._certfile,
100 'server_side': server_side,
100 'server_side': server_side,
101 'cert_reqs': self.verify_mode,
101 'cert_reqs': self.verify_mode,
102 'ssl_version': self.protocol,
102 'ssl_version': self.protocol,
103 'ca_certs': self._cacerts,
103 'ca_certs': self._cacerts,
104 }
104 }
105
105
106 if self._supportsciphers:
106 if self._supportsciphers:
107 args['ciphers'] = self._ciphers
107 args['ciphers'] = self._ciphers
108
108
109 return ssl.wrap_socket(socket, **args)
109 return ssl.wrap_socket(socket, **args)
110
110
111 def _hostsettings(ui, hostname):
111 def _hostsettings(ui, hostname):
112 """Obtain security settings for a hostname.
112 """Obtain security settings for a hostname.
113
113
114 Returns a dict of settings relevant to that hostname.
114 Returns a dict of settings relevant to that hostname.
115 """
115 """
116 s = {
116 s = {
117 # Whether we should attempt to load default/available CA certs
117 # Whether we should attempt to load default/available CA certs
118 # if an explicit ``cafile`` is not defined.
118 # if an explicit ``cafile`` is not defined.
119 'allowloaddefaultcerts': True,
119 'allowloaddefaultcerts': True,
120 # List of 2-tuple of (hash algorithm, hash).
120 # List of 2-tuple of (hash algorithm, hash).
121 'certfingerprints': [],
121 'certfingerprints': [],
122 # Path to file containing concatenated CA certs. Used by
122 # Path to file containing concatenated CA certs. Used by
123 # SSLContext.load_verify_locations().
123 # SSLContext.load_verify_locations().
124 'cafile': None,
124 'cafile': None,
125 # Whether certificate verification should be disabled.
125 # Whether certificate verification should be disabled.
126 'disablecertverification': False,
126 'disablecertverification': False,
127 # Whether the legacy [hostfingerprints] section has data for this host.
127 # Whether the legacy [hostfingerprints] section has data for this host.
128 'legacyfingerprint': False,
128 'legacyfingerprint': False,
129 # ssl.CERT_* constant used by SSLContext.verify_mode.
129 # ssl.CERT_* constant used by SSLContext.verify_mode.
130 'verifymode': None,
130 'verifymode': None,
131 }
131 }
132
132
133 # Look for fingerprints in [hostsecurity] section. Value is a list
133 # Look for fingerprints in [hostsecurity] section. Value is a list
134 # of <alg>:<fingerprint> strings.
134 # of <alg>:<fingerprint> strings.
135 fingerprints = ui.configlist('hostsecurity', '%s:fingerprints' % hostname,
135 fingerprints = ui.configlist('hostsecurity', '%s:fingerprints' % hostname,
136 [])
136 [])
137 for fingerprint in fingerprints:
137 for fingerprint in fingerprints:
138 if not (fingerprint.startswith(('sha1:', 'sha256:', 'sha512:'))):
138 if not (fingerprint.startswith(('sha1:', 'sha256:', 'sha512:'))):
139 raise error.Abort(_('invalid fingerprint for %s: %s') % (
139 raise error.Abort(_('invalid fingerprint for %s: %s') % (
140 hostname, fingerprint),
140 hostname, fingerprint),
141 hint=_('must begin with "sha1:", "sha256:", '
141 hint=_('must begin with "sha1:", "sha256:", '
142 'or "sha512:"'))
142 'or "sha512:"'))
143
143
144 alg, fingerprint = fingerprint.split(':', 1)
144 alg, fingerprint = fingerprint.split(':', 1)
145 fingerprint = fingerprint.replace(':', '').lower()
145 fingerprint = fingerprint.replace(':', '').lower()
146 s['certfingerprints'].append((alg, fingerprint))
146 s['certfingerprints'].append((alg, fingerprint))
147
147
148 # Fingerprints from [hostfingerprints] are always SHA-1.
148 # Fingerprints from [hostfingerprints] are always SHA-1.
149 for fingerprint in ui.configlist('hostfingerprints', hostname, []):
149 for fingerprint in ui.configlist('hostfingerprints', hostname, []):
150 fingerprint = fingerprint.replace(':', '').lower()
150 fingerprint = fingerprint.replace(':', '').lower()
151 s['certfingerprints'].append(('sha1', fingerprint))
151 s['certfingerprints'].append(('sha1', fingerprint))
152 s['legacyfingerprint'] = True
152 s['legacyfingerprint'] = True
153
153
154 # If a host cert fingerprint is defined, it is the only thing that
154 # If a host cert fingerprint is defined, it is the only thing that
155 # matters. No need to validate CA certs.
155 # matters. No need to validate CA certs.
156 if s['certfingerprints']:
156 if s['certfingerprints']:
157 s['verifymode'] = ssl.CERT_NONE
157 s['verifymode'] = ssl.CERT_NONE
158 s['allowloaddefaultcerts'] = False
158 s['allowloaddefaultcerts'] = False
159
159
160 # If --insecure is used, don't take CAs into consideration.
160 # If --insecure is used, don't take CAs into consideration.
161 elif ui.insecureconnections:
161 elif ui.insecureconnections:
162 s['disablecertverification'] = True
162 s['disablecertverification'] = True
163 s['verifymode'] = ssl.CERT_NONE
163 s['verifymode'] = ssl.CERT_NONE
164 s['allowloaddefaultcerts'] = False
164 s['allowloaddefaultcerts'] = False
165
165
166 if ui.configbool('devel', 'disableloaddefaultcerts'):
166 if ui.configbool('devel', 'disableloaddefaultcerts'):
167 s['allowloaddefaultcerts'] = False
167 s['allowloaddefaultcerts'] = False
168
168
169 # If both fingerprints and a per-host ca file are specified, issue a warning
169 # If both fingerprints and a per-host ca file are specified, issue a warning
170 # because users should not be surprised about what security is or isn't
170 # because users should not be surprised about what security is or isn't
171 # being performed.
171 # being performed.
172 cafile = ui.config('hostsecurity', '%s:verifycertsfile' % hostname)
172 cafile = ui.config('hostsecurity', '%s:verifycertsfile' % hostname)
173 if s['certfingerprints'] and cafile:
173 if s['certfingerprints'] and cafile:
174 ui.warn(_('(hostsecurity.%s:verifycertsfile ignored when host '
174 ui.warn(_('(hostsecurity.%s:verifycertsfile ignored when host '
175 'fingerprints defined; using host fingerprints for '
175 'fingerprints defined; using host fingerprints for '
176 'verification)\n') % hostname)
176 'verification)\n') % hostname)
177
177
178 # Try to hook up CA certificate validation unless something above
178 # Try to hook up CA certificate validation unless something above
179 # makes it not necessary.
179 # makes it not necessary.
180 if s['verifymode'] is None:
180 if s['verifymode'] is None:
181 # Look at per-host ca file first.
181 # Look at per-host ca file first.
182 if cafile:
182 if cafile:
183 cafile = util.expandpath(cafile)
183 cafile = util.expandpath(cafile)
184 if not os.path.exists(cafile):
184 if not os.path.exists(cafile):
185 raise error.Abort(_('path specified by %s does not exist: %s') %
185 raise error.Abort(_('path specified by %s does not exist: %s') %
186 ('hostsecurity.%s:verifycertsfile' % hostname,
186 ('hostsecurity.%s:verifycertsfile' % hostname,
187 cafile))
187 cafile))
188 s['cafile'] = cafile
188 s['cafile'] = cafile
189 else:
189 else:
190 # Find global certificates file in config.
190 # Find global certificates file in config.
191 cafile = ui.config('web', 'cacerts')
191 cafile = ui.config('web', 'cacerts')
192
192
193 if cafile:
193 if cafile:
194 cafile = util.expandpath(cafile)
194 cafile = util.expandpath(cafile)
195 if not os.path.exists(cafile):
195 if not os.path.exists(cafile):
196 raise error.Abort(_('could not find web.cacerts: %s') %
196 raise error.Abort(_('could not find web.cacerts: %s') %
197 cafile)
197 cafile)
198 elif s['allowloaddefaultcerts']:
198 elif s['allowloaddefaultcerts']:
199 # CAs not defined in config. Try to find system bundles.
199 # CAs not defined in config. Try to find system bundles.
200 cafile = _defaultcacerts(ui)
200 cafile = _defaultcacerts(ui)
201 if cafile:
201 if cafile:
202 ui.debug('using %s for CA file\n' % cafile)
202 ui.debug('using %s for CA file\n' % cafile)
203
203
204 s['cafile'] = cafile
204 s['cafile'] = cafile
205
205
206 # Require certificate validation if CA certs are being loaded and
206 # Require certificate validation if CA certs are being loaded and
207 # verification hasn't been disabled above.
207 # verification hasn't been disabled above.
208 if cafile or (_canloaddefaultcerts and s['allowloaddefaultcerts']):
208 if cafile or (_canloaddefaultcerts and s['allowloaddefaultcerts']):
209 s['verifymode'] = ssl.CERT_REQUIRED
209 s['verifymode'] = ssl.CERT_REQUIRED
210 else:
210 else:
211 # At this point we don't have a fingerprint, aren't being
211 # At this point we don't have a fingerprint, aren't being
212 # explicitly insecure, and can't load CA certs. Connecting
212 # explicitly insecure, and can't load CA certs. Connecting
213 # is insecure. We allow the connection and abort during
213 # is insecure. We allow the connection and abort during
214 # validation (once we have the fingerprint to print to the
214 # validation (once we have the fingerprint to print to the
215 # user).
215 # user).
216 s['verifymode'] = ssl.CERT_NONE
216 s['verifymode'] = ssl.CERT_NONE
217
217
218 assert s['verifymode'] is not None
218 assert s['verifymode'] is not None
219
219
220 return s
220 return s
221
221
222 def wrapsocket(sock, keyfile, certfile, ui, serverhostname=None):
222 def wrapsocket(sock, keyfile, certfile, ui, serverhostname=None):
223 """Add SSL/TLS to a socket.
223 """Add SSL/TLS to a socket.
224
224
225 This is a glorified wrapper for ``ssl.wrap_socket()``. It makes sane
225 This is a glorified wrapper for ``ssl.wrap_socket()``. It makes sane
226 choices based on what security options are available.
226 choices based on what security options are available.
227
227
228 In addition to the arguments supported by ``ssl.wrap_socket``, we allow
228 In addition to the arguments supported by ``ssl.wrap_socket``, we allow
229 the following additional arguments:
229 the following additional arguments:
230
230
231 * serverhostname - The expected hostname of the remote server. If the
231 * serverhostname - The expected hostname of the remote server. If the
232 server (and client) support SNI, this tells the server which certificate
232 server (and client) support SNI, this tells the server which certificate
233 to use.
233 to use.
234 """
234 """
235 if not serverhostname:
235 if not serverhostname:
236 raise error.Abort(_('serverhostname argument is required'))
236 raise error.Abort(_('serverhostname argument is required'))
237
237
238 settings = _hostsettings(ui, serverhostname)
238 settings = _hostsettings(ui, serverhostname)
239
239
240 # Despite its name, PROTOCOL_SSLv23 selects the highest protocol
240 # Despite its name, PROTOCOL_SSLv23 selects the highest protocol
241 # that both ends support, including TLS protocols. On legacy stacks,
241 # that both ends support, including TLS protocols. On legacy stacks,
242 # the highest it likely goes in TLS 1.0. On modern stacks, it can
242 # the highest it likely goes in TLS 1.0. On modern stacks, it can
243 # support TLS 1.2.
243 # support TLS 1.2.
244 #
244 #
245 # The PROTOCOL_TLSv* constants select a specific TLS version
245 # The PROTOCOL_TLSv* constants select a specific TLS version
246 # only (as opposed to multiple versions). So the method for
246 # only (as opposed to multiple versions). So the method for
247 # supporting multiple TLS versions is to use PROTOCOL_SSLv23 and
247 # supporting multiple TLS versions is to use PROTOCOL_SSLv23 and
248 # disable protocols via SSLContext.options and OP_NO_* constants.
248 # disable protocols via SSLContext.options and OP_NO_* constants.
249 # However, SSLContext.options doesn't work unless we have the
249 # However, SSLContext.options doesn't work unless we have the
250 # full/real SSLContext available to us.
250 # full/real SSLContext available to us.
251 #
251 #
252 # SSLv2 and SSLv3 are broken. We ban them outright.
252 # SSLv2 and SSLv3 are broken. We ban them outright.
253 if modernssl:
253 if modernssl:
254 protocol = ssl.PROTOCOL_SSLv23
254 protocol = ssl.PROTOCOL_SSLv23
255 else:
255 else:
256 protocol = ssl.PROTOCOL_TLSv1
256 protocol = ssl.PROTOCOL_TLSv1
257
257
258 # TODO use ssl.create_default_context() on modernssl.
258 # TODO use ssl.create_default_context() on modernssl.
259 sslcontext = SSLContext(protocol)
259 sslcontext = SSLContext(protocol)
260
260
261 # This is a no-op on old Python.
261 # This is a no-op on old Python.
262 sslcontext.options |= OP_NO_SSLv2 | OP_NO_SSLv3
262 sslcontext.options |= OP_NO_SSLv2 | OP_NO_SSLv3
263
263
264 # This still works on our fake SSLContext.
264 # This still works on our fake SSLContext.
265 sslcontext.verify_mode = settings['verifymode']
265 sslcontext.verify_mode = settings['verifymode']
266
266
267 if certfile is not None:
267 if certfile is not None:
268 def password():
268 def password():
269 f = keyfile or certfile
269 f = keyfile or certfile
270 return ui.getpass(_('passphrase for %s: ') % f, '')
270 return ui.getpass(_('passphrase for %s: ') % f, '')
271 sslcontext.load_cert_chain(certfile, keyfile, password)
271 sslcontext.load_cert_chain(certfile, keyfile, password)
272
272
273 if settings['cafile'] is not None:
273 if settings['cafile'] is not None:
274 try:
274 try:
275 sslcontext.load_verify_locations(cafile=settings['cafile'])
275 sslcontext.load_verify_locations(cafile=settings['cafile'])
276 except ssl.SSLError as e:
276 except ssl.SSLError as e:
277 raise error.Abort(_('error loading CA file %s: %s') % (
277 raise error.Abort(_('error loading CA file %s: %s') % (
278 settings['cafile'], e.args[1]),
278 settings['cafile'], e.args[1]),
279 hint=_('file is empty or malformed?'))
279 hint=_('file is empty or malformed?'))
280 caloaded = True
280 caloaded = True
281 elif settings['allowloaddefaultcerts']:
281 elif settings['allowloaddefaultcerts']:
282 # This is a no-op on old Python.
282 # This is a no-op on old Python.
283 sslcontext.load_default_certs()
283 sslcontext.load_default_certs()
284 caloaded = True
284 caloaded = True
285 else:
285 else:
286 caloaded = False
286 caloaded = False
287
287
288 try:
288 try:
289 sslsocket = sslcontext.wrap_socket(sock, server_hostname=serverhostname)
289 sslsocket = sslcontext.wrap_socket(sock, server_hostname=serverhostname)
290 except ssl.SSLError:
290 except ssl.SSLError:
291 # If we're doing certificate verification and no CA certs are loaded,
291 # If we're doing certificate verification and no CA certs are loaded,
292 # that is almost certainly the reason why verification failed. Provide
292 # that is almost certainly the reason why verification failed. Provide
293 # a hint to the user.
293 # a hint to the user.
294 # Only modern ssl module exposes SSLContext.get_ca_certs() so we can
294 # Only modern ssl module exposes SSLContext.get_ca_certs() so we can
295 # only show this warning if modern ssl is available.
295 # only show this warning if modern ssl is available.
296 if (caloaded and settings['verifymode'] == ssl.CERT_REQUIRED and
296 if (caloaded and settings['verifymode'] == ssl.CERT_REQUIRED and
297 modernssl and not sslcontext.get_ca_certs()):
297 modernssl and not sslcontext.get_ca_certs()):
298 ui.warn(_('(an attempt was made to load CA certificates but none '
298 ui.warn(_('(an attempt was made to load CA certificates but none '
299 'were loaded; see '
299 'were loaded; see '
300 'https://mercurial-scm.org/wiki/SecureConnections for '
300 'https://mercurial-scm.org/wiki/SecureConnections for '
301 'how to configure Mercurial to avoid this error)\n'))
301 'how to configure Mercurial to avoid this error)\n'))
302 raise
302 raise
303
303
304 # check if wrap_socket failed silently because socket had been
304 # check if wrap_socket failed silently because socket had been
305 # closed
305 # closed
306 # - see http://bugs.python.org/issue13721
306 # - see http://bugs.python.org/issue13721
307 if not sslsocket.cipher():
307 if not sslsocket.cipher():
308 raise error.Abort(_('ssl connection failed'))
308 raise error.Abort(_('ssl connection failed'))
309
309
310 sslsocket._hgstate = {
310 sslsocket._hgstate = {
311 'caloaded': caloaded,
311 'caloaded': caloaded,
312 'hostname': serverhostname,
312 'hostname': serverhostname,
313 'settings': settings,
313 'settings': settings,
314 'ui': ui,
314 'ui': ui,
315 }
315 }
316
316
317 return sslsocket
317 return sslsocket
318
318
319 class wildcarderror(Exception):
319 class wildcarderror(Exception):
320 """Represents an error parsing wildcards in DNS name."""
320 """Represents an error parsing wildcards in DNS name."""
321
321
322 def _dnsnamematch(dn, hostname, maxwildcards=1):
322 def _dnsnamematch(dn, hostname, maxwildcards=1):
323 """Match DNS names according RFC 6125 section 6.4.3.
323 """Match DNS names according RFC 6125 section 6.4.3.
324
324
325 This code is effectively copied from CPython's ssl._dnsname_match.
325 This code is effectively copied from CPython's ssl._dnsname_match.
326
326
327 Returns a bool indicating whether the expected hostname matches
327 Returns a bool indicating whether the expected hostname matches
328 the value in ``dn``.
328 the value in ``dn``.
329 """
329 """
330 pats = []
330 pats = []
331 if not dn:
331 if not dn:
332 return False
332 return False
333
333
334 pieces = dn.split(r'.')
334 pieces = dn.split(r'.')
335 leftmost = pieces[0]
335 leftmost = pieces[0]
336 remainder = pieces[1:]
336 remainder = pieces[1:]
337 wildcards = leftmost.count('*')
337 wildcards = leftmost.count('*')
338 if wildcards > maxwildcards:
338 if wildcards > maxwildcards:
339 raise wildcarderror(
339 raise wildcarderror(
340 _('too many wildcards in certificate DNS name: %s') % dn)
340 _('too many wildcards in certificate DNS name: %s') % dn)
341
341
342 # speed up common case w/o wildcards
342 # speed up common case w/o wildcards
343 if not wildcards:
343 if not wildcards:
344 return dn.lower() == hostname.lower()
344 return dn.lower() == hostname.lower()
345
345
346 # RFC 6125, section 6.4.3, subitem 1.
346 # RFC 6125, section 6.4.3, subitem 1.
347 # The client SHOULD NOT attempt to match a presented identifier in which
347 # The client SHOULD NOT attempt to match a presented identifier in which
348 # the wildcard character comprises a label other than the left-most label.
348 # the wildcard character comprises a label other than the left-most label.
349 if leftmost == '*':
349 if leftmost == '*':
350 # When '*' is a fragment by itself, it matches a non-empty dotless
350 # When '*' is a fragment by itself, it matches a non-empty dotless
351 # fragment.
351 # fragment.
352 pats.append('[^.]+')
352 pats.append('[^.]+')
353 elif leftmost.startswith('xn--') or hostname.startswith('xn--'):
353 elif leftmost.startswith('xn--') or hostname.startswith('xn--'):
354 # RFC 6125, section 6.4.3, subitem 3.
354 # RFC 6125, section 6.4.3, subitem 3.
355 # The client SHOULD NOT attempt to match a presented identifier
355 # The client SHOULD NOT attempt to match a presented identifier
356 # where the wildcard character is embedded within an A-label or
356 # where the wildcard character is embedded within an A-label or
357 # U-label of an internationalized domain name.
357 # U-label of an internationalized domain name.
358 pats.append(re.escape(leftmost))
358 pats.append(re.escape(leftmost))
359 else:
359 else:
360 # Otherwise, '*' matches any dotless string, e.g. www*
360 # Otherwise, '*' matches any dotless string, e.g. www*
361 pats.append(re.escape(leftmost).replace(r'\*', '[^.]*'))
361 pats.append(re.escape(leftmost).replace(r'\*', '[^.]*'))
362
362
363 # add the remaining fragments, ignore any wildcards
363 # add the remaining fragments, ignore any wildcards
364 for frag in remainder:
364 for frag in remainder:
365 pats.append(re.escape(frag))
365 pats.append(re.escape(frag))
366
366
367 pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)
367 pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)
368 return pat.match(hostname) is not None
368 return pat.match(hostname) is not None
369
369
370 def _verifycert(cert, hostname):
370 def _verifycert(cert, hostname):
371 '''Verify that cert (in socket.getpeercert() format) matches hostname.
371 '''Verify that cert (in socket.getpeercert() format) matches hostname.
372 CRLs is not handled.
372 CRLs is not handled.
373
373
374 Returns error message if any problems are found and None on success.
374 Returns error message if any problems are found and None on success.
375 '''
375 '''
376 if not cert:
376 if not cert:
377 return _('no certificate received')
377 return _('no certificate received')
378
378
379 dnsnames = []
379 dnsnames = []
380 san = cert.get('subjectAltName', [])
380 san = cert.get('subjectAltName', [])
381 for key, value in san:
381 for key, value in san:
382 if key == 'DNS':
382 if key == 'DNS':
383 try:
383 try:
384 if _dnsnamematch(value, hostname):
384 if _dnsnamematch(value, hostname):
385 return
385 return
386 except wildcarderror as e:
386 except wildcarderror as e:
387 return e.message
387 return e.message
388
388
389 dnsnames.append(value)
389 dnsnames.append(value)
390
390
391 if not dnsnames:
391 if not dnsnames:
392 # The subject is only checked when there is no DNS in subjectAltName.
392 # The subject is only checked when there is no DNS in subjectAltName.
393 for sub in cert.get('subject', []):
393 for sub in cert.get('subject', []):
394 for key, value in sub:
394 for key, value in sub:
395 # According to RFC 2818 the most specific Common Name must
395 # According to RFC 2818 the most specific Common Name must
396 # be used.
396 # be used.
397 if key == 'commonName':
397 if key == 'commonName':
398 # 'subject' entries are unicide.
398 # 'subject' entries are unicide.
399 try:
399 try:
400 value = value.encode('ascii')
400 value = value.encode('ascii')
401 except UnicodeEncodeError:
401 except UnicodeEncodeError:
402 return _('IDN in certificate not supported')
402 return _('IDN in certificate not supported')
403
403
404 try:
404 try:
405 if _dnsnamematch(value, hostname):
405 if _dnsnamematch(value, hostname):
406 return
406 return
407 except wildcarderror as e:
407 except wildcarderror as e:
408 return e.message
408 return e.message
409
409
410 dnsnames.append(value)
410 dnsnames.append(value)
411
411
412 if len(dnsnames) > 1:
412 if len(dnsnames) > 1:
413 return _('certificate is for %s') % ', '.join(dnsnames)
413 return _('certificate is for %s') % ', '.join(dnsnames)
414 elif len(dnsnames) == 1:
414 elif len(dnsnames) == 1:
415 return _('certificate is for %s') % dnsnames[0]
415 return _('certificate is for %s') % dnsnames[0]
416 else:
416 else:
417 return _('no commonName or subjectAltName found in certificate')
417 return _('no commonName or subjectAltName found in certificate')
418
418
419 def _plainapplepython():
419 def _plainapplepython():
420 """return true if this seems to be a pure Apple Python that
420 """return true if this seems to be a pure Apple Python that
421 * is unfrozen and presumably has the whole mercurial module in the file
421 * is unfrozen and presumably has the whole mercurial module in the file
422 system
422 system
423 * presumably is an Apple Python that uses Apple OpenSSL which has patches
423 * presumably is an Apple Python that uses Apple OpenSSL which has patches
424 for using system certificate store CAs in addition to the provided
424 for using system certificate store CAs in addition to the provided
425 cacerts file
425 cacerts file
426 """
426 """
427 if sys.platform != 'darwin' or util.mainfrozen() or not sys.executable:
427 if sys.platform != 'darwin' or util.mainfrozen() or not sys.executable:
428 return False
428 return False
429 exe = os.path.realpath(sys.executable).lower()
429 exe = os.path.realpath(sys.executable).lower()
430 return (exe.startswith('/usr/bin/python') or
430 return (exe.startswith('/usr/bin/python') or
431 exe.startswith('/system/library/frameworks/python.framework/'))
431 exe.startswith('/system/library/frameworks/python.framework/'))
432
432
433 _systemcacertpaths = [
434 # RHEL, CentOS, and Fedora
435 '/etc/pki/tls/certs/ca-bundle.trust.crt',
436 # Debian, Ubuntu, Gentoo
437 '/etc/ssl/certs/ca-certificates.crt',
438 ]
439
433 def _defaultcacerts(ui):
440 def _defaultcacerts(ui):
434 """return path to default CA certificates or None.
441 """return path to default CA certificates or None.
435
442
436 It is assumed this function is called when the returned certificates
443 It is assumed this function is called when the returned certificates
437 file will actually be used to validate connections. Therefore this
444 file will actually be used to validate connections. Therefore this
438 function may print warnings or debug messages assuming this usage.
445 function may print warnings or debug messages assuming this usage.
446
447 We don't print a message when the Python is able to load default
448 CA certs because this scenario is detected at socket connect time.
439 """
449 """
440 # The "certifi" Python package provides certificates. If it is installed,
450 # The "certifi" Python package provides certificates. If it is installed,
441 # assume the user intends it to be used and use it.
451 # assume the user intends it to be used and use it.
442 try:
452 try:
443 import certifi
453 import certifi
444 certs = certifi.where()
454 certs = certifi.where()
445 ui.debug('using ca certificates from certifi\n')
455 ui.debug('using ca certificates from certifi\n')
446 return certs
456 return certs
447 except ImportError:
457 except ImportError:
448 pass
458 pass
449
459
450 # On Windows, only the modern ssl module is capable of loading the system
460 # On Windows, only the modern ssl module is capable of loading the system
451 # CA certificates. If we're not capable of doing that, emit a warning
461 # CA certificates. If we're not capable of doing that, emit a warning
452 # because we'll get a certificate verification error later and the lack
462 # because we'll get a certificate verification error later and the lack
453 # of loaded CA certificates will be the reason why.
463 # of loaded CA certificates will be the reason why.
454 # Assertion: this code is only called if certificates are being verified.
464 # Assertion: this code is only called if certificates are being verified.
455 if os.name == 'nt':
465 if os.name == 'nt':
456 if not _canloaddefaultcerts:
466 if not _canloaddefaultcerts:
457 ui.warn(_('(unable to load Windows CA certificates; see '
467 ui.warn(_('(unable to load Windows CA certificates; see '
458 'https://mercurial-scm.org/wiki/SecureConnections for '
468 'https://mercurial-scm.org/wiki/SecureConnections for '
459 'how to configure Mercurial to avoid this message)\n'))
469 'how to configure Mercurial to avoid this message)\n'))
460
470
461 return None
471 return None
462
472
463 # Apple's OpenSSL has patches that allow a specially constructed certificate
473 # Apple's OpenSSL has patches that allow a specially constructed certificate
464 # to load the system CA store. If we're running on Apple Python, use this
474 # to load the system CA store. If we're running on Apple Python, use this
465 # trick.
475 # trick.
466 if _plainapplepython():
476 if _plainapplepython():
467 dummycert = os.path.join(os.path.dirname(__file__), 'dummycert.pem')
477 dummycert = os.path.join(os.path.dirname(__file__), 'dummycert.pem')
468 if os.path.exists(dummycert):
478 if os.path.exists(dummycert):
469 return dummycert
479 return dummycert
470
480
471 # The Apple OpenSSL trick isn't available to us. If Python isn't able to
481 # The Apple OpenSSL trick isn't available to us. If Python isn't able to
472 # load system certs, we're out of luck.
482 # load system certs, we're out of luck.
473 if sys.platform == 'darwin':
483 if sys.platform == 'darwin':
474 # FUTURE Consider looking for Homebrew or MacPorts installed certs
484 # FUTURE Consider looking for Homebrew or MacPorts installed certs
475 # files. Also consider exporting the keychain certs to a file during
485 # files. Also consider exporting the keychain certs to a file during
476 # Mercurial install.
486 # Mercurial install.
477 if not _canloaddefaultcerts:
487 if not _canloaddefaultcerts:
478 ui.warn(_('(unable to load CA certificates; see '
488 ui.warn(_('(unable to load CA certificates; see '
479 'https://mercurial-scm.org/wiki/SecureConnections for '
489 'https://mercurial-scm.org/wiki/SecureConnections for '
480 'how to configure Mercurial to avoid this message)\n'))
490 'how to configure Mercurial to avoid this message)\n'))
481 return None
491 return None
482
492
493 # Try to find CA certificates in well-known locations. We print a warning
494 # when using a found file because we don't want too much silent magic
495 # for security settings. The expectation is that proper Mercurial
496 # installs will have the CA certs path defined at install time and the
497 # installer/packager will make an appropriate decision on the user's
498 # behalf. We only get here and perform this setting as a feature of
499 # last resort.
500 if not _canloaddefaultcerts:
501 for path in _systemcacertpaths:
502 if os.path.isfile(path):
503 ui.warn(_('(using CA certificates from %s; if you see this '
504 'message, your Mercurial install is not properly '
505 'configured; see '
506 'https://mercurial-scm.org/wiki/SecureConnections '
507 'for how to configure Mercurial to avoid this '
508 'message)\n') % path)
509 return path
510
511 ui.warn(_('(unable to load CA certificates; see '
512 'https://mercurial-scm.org/wiki/SecureConnections for '
513 'how to configure Mercurial to avoid this message)\n'))
514
483 return None
515 return None
484
516
485 def validatesocket(sock):
517 def validatesocket(sock):
486 """Validate a socket meets security requiremnets.
518 """Validate a socket meets security requiremnets.
487
519
488 The passed socket must have been created with ``wrapsocket()``.
520 The passed socket must have been created with ``wrapsocket()``.
489 """
521 """
490 host = sock._hgstate['hostname']
522 host = sock._hgstate['hostname']
491 ui = sock._hgstate['ui']
523 ui = sock._hgstate['ui']
492 settings = sock._hgstate['settings']
524 settings = sock._hgstate['settings']
493
525
494 try:
526 try:
495 peercert = sock.getpeercert(True)
527 peercert = sock.getpeercert(True)
496 peercert2 = sock.getpeercert()
528 peercert2 = sock.getpeercert()
497 except AttributeError:
529 except AttributeError:
498 raise error.Abort(_('%s ssl connection error') % host)
530 raise error.Abort(_('%s ssl connection error') % host)
499
531
500 if not peercert:
532 if not peercert:
501 raise error.Abort(_('%s certificate error: '
533 raise error.Abort(_('%s certificate error: '
502 'no certificate received') % host)
534 'no certificate received') % host)
503
535
504 if settings['disablecertverification']:
536 if settings['disablecertverification']:
505 # We don't print the certificate fingerprint because it shouldn't
537 # We don't print the certificate fingerprint because it shouldn't
506 # be necessary: if the user requested certificate verification be
538 # be necessary: if the user requested certificate verification be
507 # disabled, they presumably already saw a message about the inability
539 # disabled, they presumably already saw a message about the inability
508 # to verify the certificate and this message would have printed the
540 # to verify the certificate and this message would have printed the
509 # fingerprint. So printing the fingerprint here adds little to no
541 # fingerprint. So printing the fingerprint here adds little to no
510 # value.
542 # value.
511 ui.warn(_('warning: connection security to %s is disabled per current '
543 ui.warn(_('warning: connection security to %s is disabled per current '
512 'settings; communication is susceptible to eavesdropping '
544 'settings; communication is susceptible to eavesdropping '
513 'and tampering\n') % host)
545 'and tampering\n') % host)
514 return
546 return
515
547
516 # If a certificate fingerprint is pinned, use it and only it to
548 # If a certificate fingerprint is pinned, use it and only it to
517 # validate the remote cert.
549 # validate the remote cert.
518 peerfingerprints = {
550 peerfingerprints = {
519 'sha1': hashlib.sha1(peercert).hexdigest(),
551 'sha1': hashlib.sha1(peercert).hexdigest(),
520 'sha256': hashlib.sha256(peercert).hexdigest(),
552 'sha256': hashlib.sha256(peercert).hexdigest(),
521 'sha512': hashlib.sha512(peercert).hexdigest(),
553 'sha512': hashlib.sha512(peercert).hexdigest(),
522 }
554 }
523
555
524 def fmtfingerprint(s):
556 def fmtfingerprint(s):
525 return ':'.join([s[x:x + 2] for x in range(0, len(s), 2)])
557 return ':'.join([s[x:x + 2] for x in range(0, len(s), 2)])
526
558
527 nicefingerprint = 'sha256:%s' % fmtfingerprint(peerfingerprints['sha256'])
559 nicefingerprint = 'sha256:%s' % fmtfingerprint(peerfingerprints['sha256'])
528
560
529 if settings['certfingerprints']:
561 if settings['certfingerprints']:
530 for hash, fingerprint in settings['certfingerprints']:
562 for hash, fingerprint in settings['certfingerprints']:
531 if peerfingerprints[hash].lower() == fingerprint:
563 if peerfingerprints[hash].lower() == fingerprint:
532 ui.debug('%s certificate matched fingerprint %s:%s\n' %
564 ui.debug('%s certificate matched fingerprint %s:%s\n' %
533 (host, hash, fmtfingerprint(fingerprint)))
565 (host, hash, fmtfingerprint(fingerprint)))
534 return
566 return
535
567
536 # Pinned fingerprint didn't match. This is a fatal error.
568 # Pinned fingerprint didn't match. This is a fatal error.
537 if settings['legacyfingerprint']:
569 if settings['legacyfingerprint']:
538 section = 'hostfingerprint'
570 section = 'hostfingerprint'
539 nice = fmtfingerprint(peerfingerprints['sha1'])
571 nice = fmtfingerprint(peerfingerprints['sha1'])
540 else:
572 else:
541 section = 'hostsecurity'
573 section = 'hostsecurity'
542 nice = '%s:%s' % (hash, fmtfingerprint(peerfingerprints[hash]))
574 nice = '%s:%s' % (hash, fmtfingerprint(peerfingerprints[hash]))
543 raise error.Abort(_('certificate for %s has unexpected '
575 raise error.Abort(_('certificate for %s has unexpected '
544 'fingerprint %s') % (host, nice),
576 'fingerprint %s') % (host, nice),
545 hint=_('check %s configuration') % section)
577 hint=_('check %s configuration') % section)
546
578
547 # Security is enabled but no CAs are loaded. We can't establish trust
579 # Security is enabled but no CAs are loaded. We can't establish trust
548 # for the cert so abort.
580 # for the cert so abort.
549 if not sock._hgstate['caloaded']:
581 if not sock._hgstate['caloaded']:
550 raise error.Abort(
582 raise error.Abort(
551 _('unable to verify security of %s (no loaded CA certificates); '
583 _('unable to verify security of %s (no loaded CA certificates); '
552 'refusing to connect') % host,
584 'refusing to connect') % host,
553 hint=_('see https://mercurial-scm.org/wiki/SecureConnections for '
585 hint=_('see https://mercurial-scm.org/wiki/SecureConnections for '
554 'how to configure Mercurial to avoid this error or set '
586 'how to configure Mercurial to avoid this error or set '
555 'hostsecurity.%s:fingerprints=%s to trust this server') %
587 'hostsecurity.%s:fingerprints=%s to trust this server') %
556 (host, nicefingerprint))
588 (host, nicefingerprint))
557
589
558 msg = _verifycert(peercert2, host)
590 msg = _verifycert(peercert2, host)
559 if msg:
591 if msg:
560 raise error.Abort(_('%s certificate error: %s') % (host, msg),
592 raise error.Abort(_('%s certificate error: %s') % (host, msg),
561 hint=_('set hostsecurity.%s:certfingerprints=%s '
593 hint=_('set hostsecurity.%s:certfingerprints=%s '
562 'config setting or use --insecure to connect '
594 'config setting or use --insecure to connect '
563 'insecurely') %
595 'insecurely') %
564 (host, nicefingerprint))
596 (host, nicefingerprint))
@@ -1,448 +1,450
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 abort: could not find web.cacerts: no-such.pem
31 abort: could not find web.cacerts: no-such.pem
32 [255]
32 [255]
33
33
34 Test server address cannot be reused
34 Test server address cannot be reused
35
35
36 #if windows
36 #if windows
37 $ hg serve -p $HGPORT --certificate=$PRIV 2>&1
37 $ hg serve -p $HGPORT --certificate=$PRIV 2>&1
38 abort: cannot start server at ':$HGPORT':
38 abort: cannot start server at ':$HGPORT':
39 [255]
39 [255]
40 #else
40 #else
41 $ hg serve -p $HGPORT --certificate=$PRIV 2>&1
41 $ hg serve -p $HGPORT --certificate=$PRIV 2>&1
42 abort: cannot start server at ':$HGPORT': Address already in use
42 abort: cannot start server at ':$HGPORT': Address already in use
43 [255]
43 [255]
44 #endif
44 #endif
45 $ cd ..
45 $ cd ..
46
46
47 Our test cert is not signed by a trusted CA. It should fail to verify if
47 Our test cert is not signed by a trusted CA. It should fail to verify if
48 we are able to load CA certs.
48 we are able to load CA certs.
49
49
50 #if sslcontext defaultcacerts no-defaultcacertsloaded
50 #if sslcontext defaultcacerts no-defaultcacertsloaded
51 $ hg clone https://localhost:$HGPORT/ copy-pull
51 $ hg clone https://localhost:$HGPORT/ copy-pull
52 (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)
52 (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 abort: error: *certificate verify failed* (glob)
53 abort: error: *certificate verify failed* (glob)
54 [255]
54 [255]
55 #endif
55 #endif
56
56
57 #if no-sslcontext defaultcacerts
57 #if no-sslcontext defaultcacerts
58 $ hg clone https://localhost:$HGPORT/ copy-pull
58 $ hg clone https://localhost:$HGPORT/ copy-pull
59 (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) (?)
59 abort: error: *certificate verify failed* (glob)
60 abort: error: *certificate verify failed* (glob)
60 [255]
61 [255]
61 #endif
62 #endif
62
63
63 #if no-sslcontext windows
64 #if no-sslcontext windows
64 $ hg clone https://localhost:$HGPORT/ copy-pull
65 $ hg clone https://localhost:$HGPORT/ copy-pull
65 (unable to load Windows CA certificates; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message)
66 (unable to load Windows CA certificates; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message)
66 abort: error: *certificate verify failed* (glob)
67 abort: error: *certificate verify failed* (glob)
67 [255]
68 [255]
68 #endif
69 #endif
69
70
70 #if no-sslcontext osx
71 #if no-sslcontext osx
71 $ hg clone https://localhost:$HGPORT/ copy-pull
72 $ hg clone https://localhost:$HGPORT/ copy-pull
72 (unable to load CA certificates; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message)
73 (unable to load CA certificates; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message)
73 abort: localhost certificate error: no certificate received
74 abort: localhost certificate error: no certificate received
74 (set hostsecurity.localhost:certfingerprints=sha256:62:09:97:2f:97:60:e3:65:8f:12:5d:78:9e:35:a1:36:7a:65:4b:0e:9f:ac:db:c3:bc:6e:b6:a3:c0:16:e0:30 config setting or use --insecure to connect insecurely)
75 (set hostsecurity.localhost:certfingerprints=sha256:62:09:97:2f:97:60:e3:65:8f:12:5d:78:9e:35:a1:36:7a:65:4b:0e:9f:ac:db:c3:bc:6e:b6:a3:c0:16:e0:30 config setting or use --insecure to connect insecurely)
75 [255]
76 [255]
76 #endif
77 #endif
77
78
78 #if defaultcacertsloaded
79 #if defaultcacertsloaded
79 $ hg clone https://localhost:$HGPORT/ copy-pull
80 $ hg clone https://localhost:$HGPORT/ copy-pull
81 (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) (?)
80 abort: error: *certificate verify failed* (glob)
82 abort: error: *certificate verify failed* (glob)
81 [255]
83 [255]
82 #endif
84 #endif
83
85
84 #if no-defaultcacerts
86 #if no-defaultcacerts
85 $ hg clone https://localhost:$HGPORT/ copy-pull
87 $ hg clone https://localhost:$HGPORT/ copy-pull
86 (unable to load * certificates; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message) (glob) (?)
88 (unable to load * certificates; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message) (glob) (?)
87 abort: localhost certificate error: no certificate received
89 abort: localhost certificate error: no certificate received
88 (set hostsecurity.localhost:certfingerprints=sha256:62:09:97:2f:97:60:e3:65:8f:12:5d:78:9e:35:a1:36:7a:65:4b:0e:9f:ac:db:c3:bc:6e:b6:a3:c0:16:e0:30 config setting or use --insecure to connect insecurely)
90 (set hostsecurity.localhost:certfingerprints=sha256:62:09:97:2f:97:60:e3:65:8f:12:5d:78:9e:35:a1:36:7a:65:4b:0e:9f:ac:db:c3:bc:6e:b6:a3:c0:16:e0:30 config setting or use --insecure to connect insecurely)
89 [255]
91 [255]
90 #endif
92 #endif
91
93
92 Specifying a per-host certificate file that doesn't exist will abort
94 Specifying a per-host certificate file that doesn't exist will abort
93
95
94 $ hg --config hostsecurity.localhost:verifycertsfile=/does/not/exist clone https://localhost:$HGPORT/
96 $ hg --config hostsecurity.localhost:verifycertsfile=/does/not/exist clone https://localhost:$HGPORT/
95 abort: path specified by hostsecurity.localhost:verifycertsfile does not exist: /does/not/exist
97 abort: path specified by hostsecurity.localhost:verifycertsfile does not exist: /does/not/exist
96 [255]
98 [255]
97
99
98 A malformed per-host certificate file will raise an error
100 A malformed per-host certificate file will raise an error
99
101
100 $ echo baddata > badca.pem
102 $ echo baddata > badca.pem
101 #if sslcontext
103 #if sslcontext
102 $ hg --config hostsecurity.localhost:verifycertsfile=badca.pem clone https://localhost:$HGPORT/
104 $ hg --config hostsecurity.localhost:verifycertsfile=badca.pem clone https://localhost:$HGPORT/
103 abort: error loading CA file badca.pem: * (glob)
105 abort: error loading CA file badca.pem: * (glob)
104 (file is empty or malformed?)
106 (file is empty or malformed?)
105 [255]
107 [255]
106 #else
108 #else
107 $ hg --config hostsecurity.localhost:verifycertsfile=badca.pem clone https://localhost:$HGPORT/
109 $ hg --config hostsecurity.localhost:verifycertsfile=badca.pem clone https://localhost:$HGPORT/
108 abort: error: * (glob)
110 abort: error: * (glob)
109 [255]
111 [255]
110 #endif
112 #endif
111
113
112 A per-host certificate mismatching the server will fail verification
114 A per-host certificate mismatching the server will fail verification
113
115
114 (modern ssl is able to discern whether the loaded cert is a CA cert)
116 (modern ssl is able to discern whether the loaded cert is a CA cert)
115 #if sslcontext
117 #if sslcontext
116 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/client-cert.pem" clone https://localhost:$HGPORT/
118 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/client-cert.pem" clone https://localhost:$HGPORT/
117 (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)
119 (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)
118 abort: error: *certificate verify failed* (glob)
120 abort: error: *certificate verify failed* (glob)
119 [255]
121 [255]
120 #else
122 #else
121 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/client-cert.pem" clone https://localhost:$HGPORT/
123 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/client-cert.pem" clone https://localhost:$HGPORT/
122 abort: error: *certificate verify failed* (glob)
124 abort: error: *certificate verify failed* (glob)
123 [255]
125 [255]
124 #endif
126 #endif
125
127
126 A per-host certificate matching the server's cert will be accepted
128 A per-host certificate matching the server's cert will be accepted
127
129
128 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/pub.pem" clone -U https://localhost:$HGPORT/ perhostgood1
130 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/pub.pem" clone -U https://localhost:$HGPORT/ perhostgood1
129 requesting all changes
131 requesting all changes
130 adding changesets
132 adding changesets
131 adding manifests
133 adding manifests
132 adding file changes
134 adding file changes
133 added 1 changesets with 4 changes to 4 files
135 added 1 changesets with 4 changes to 4 files
134
136
135 A per-host certificate with multiple certs and one matching will be accepted
137 A per-host certificate with multiple certs and one matching will be accepted
136
138
137 $ cat "$CERTSDIR/client-cert.pem" "$CERTSDIR/pub.pem" > perhost.pem
139 $ cat "$CERTSDIR/client-cert.pem" "$CERTSDIR/pub.pem" > perhost.pem
138 $ hg --config hostsecurity.localhost:verifycertsfile=perhost.pem clone -U https://localhost:$HGPORT/ perhostgood2
140 $ hg --config hostsecurity.localhost:verifycertsfile=perhost.pem clone -U https://localhost:$HGPORT/ perhostgood2
139 requesting all changes
141 requesting all changes
140 adding changesets
142 adding changesets
141 adding manifests
143 adding manifests
142 adding file changes
144 adding file changes
143 added 1 changesets with 4 changes to 4 files
145 added 1 changesets with 4 changes to 4 files
144
146
145 Defining both per-host certificate and a fingerprint will print a warning
147 Defining both per-host certificate and a fingerprint will print a warning
146
148
147 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/pub.pem" --config hostsecurity.localhost:fingerprints=sha1:914f1aff87249c09b6859b88b1906d30756491ca clone -U https://localhost:$HGPORT/ caandfingerwarning
149 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/pub.pem" --config hostsecurity.localhost:fingerprints=sha1:914f1aff87249c09b6859b88b1906d30756491ca clone -U https://localhost:$HGPORT/ caandfingerwarning
148 (hostsecurity.localhost:verifycertsfile ignored when host fingerprints defined; using host fingerprints for verification)
150 (hostsecurity.localhost:verifycertsfile ignored when host fingerprints defined; using host fingerprints for verification)
149 requesting all changes
151 requesting all changes
150 adding changesets
152 adding changesets
151 adding manifests
153 adding manifests
152 adding file changes
154 adding file changes
153 added 1 changesets with 4 changes to 4 files
155 added 1 changesets with 4 changes to 4 files
154
156
155 $ DISABLECACERTS="--config devel.disableloaddefaultcerts=true"
157 $ DISABLECACERTS="--config devel.disableloaddefaultcerts=true"
156
158
157 Inability to verify peer certificate will result in abort
159 Inability to verify peer certificate will result in abort
158
160
159 $ hg clone https://localhost:$HGPORT/ copy-pull $DISABLECACERTS
161 $ hg clone https://localhost:$HGPORT/ copy-pull $DISABLECACERTS
160 abort: unable to verify security of localhost (no loaded CA certificates); refusing to connect
162 abort: unable to verify security of localhost (no loaded CA certificates); refusing to connect
161 (see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error or set hostsecurity.localhost:fingerprints=sha256:62:09:97:2f:97:60:e3:65:8f:12:5d:78:9e:35:a1:36:7a:65:4b:0e:9f:ac:db:c3:bc:6e:b6:a3:c0:16:e0:30 to trust this server)
163 (see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error or set hostsecurity.localhost:fingerprints=sha256:62:09:97:2f:97:60:e3:65:8f:12:5d:78:9e:35:a1:36:7a:65:4b:0e:9f:ac:db:c3:bc:6e:b6:a3:c0:16:e0:30 to trust this server)
162 [255]
164 [255]
163
165
164 $ hg clone --insecure https://localhost:$HGPORT/ copy-pull
166 $ hg clone --insecure https://localhost:$HGPORT/ copy-pull
165 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
167 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
166 requesting all changes
168 requesting all changes
167 adding changesets
169 adding changesets
168 adding manifests
170 adding manifests
169 adding file changes
171 adding file changes
170 added 1 changesets with 4 changes to 4 files
172 added 1 changesets with 4 changes to 4 files
171 updating to branch default
173 updating to branch default
172 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
174 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
173 $ hg verify -R copy-pull
175 $ hg verify -R copy-pull
174 checking changesets
176 checking changesets
175 checking manifests
177 checking manifests
176 crosschecking files in changesets and manifests
178 crosschecking files in changesets and manifests
177 checking files
179 checking files
178 4 files, 1 changesets, 4 total revisions
180 4 files, 1 changesets, 4 total revisions
179 $ cd test
181 $ cd test
180 $ echo bar > bar
182 $ echo bar > bar
181 $ hg commit -A -d '1 0' -m 2
183 $ hg commit -A -d '1 0' -m 2
182 adding bar
184 adding bar
183 $ cd ..
185 $ cd ..
184
186
185 pull without cacert
187 pull without cacert
186
188
187 $ cd copy-pull
189 $ cd copy-pull
188 $ echo '[hooks]' >> .hg/hgrc
190 $ echo '[hooks]' >> .hg/hgrc
189 $ echo "changegroup = printenv.py changegroup" >> .hg/hgrc
191 $ echo "changegroup = printenv.py changegroup" >> .hg/hgrc
190 $ hg pull $DISABLECACERTS
192 $ hg pull $DISABLECACERTS
191 pulling from https://localhost:$HGPORT/
193 pulling from https://localhost:$HGPORT/
192 abort: unable to verify security of localhost (no loaded CA certificates); refusing to connect
194 abort: unable to verify security of localhost (no loaded CA certificates); refusing to connect
193 (see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error or set hostsecurity.localhost:fingerprints=sha256:62:09:97:2f:97:60:e3:65:8f:12:5d:78:9e:35:a1:36:7a:65:4b:0e:9f:ac:db:c3:bc:6e:b6:a3:c0:16:e0:30 to trust this server)
195 (see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error or set hostsecurity.localhost:fingerprints=sha256:62:09:97:2f:97:60:e3:65:8f:12:5d:78:9e:35:a1:36:7a:65:4b:0e:9f:ac:db:c3:bc:6e:b6:a3:c0:16:e0:30 to trust this server)
194 [255]
196 [255]
195
197
196 $ hg pull --insecure
198 $ hg pull --insecure
197 pulling from https://localhost:$HGPORT/
199 pulling from https://localhost:$HGPORT/
198 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
200 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
199 searching for changes
201 searching for changes
200 adding changesets
202 adding changesets
201 adding manifests
203 adding manifests
202 adding file changes
204 adding file changes
203 added 1 changesets with 1 changes to 1 files
205 added 1 changesets with 1 changes to 1 files
204 changegroup hook: HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_NODE_LAST=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=https://localhost:$HGPORT/ (glob)
206 changegroup hook: HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_NODE_LAST=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=https://localhost:$HGPORT/ (glob)
205 (run 'hg update' to get a working copy)
207 (run 'hg update' to get a working copy)
206 $ cd ..
208 $ cd ..
207
209
208 cacert configured in local repo
210 cacert configured in local repo
209
211
210 $ cp copy-pull/.hg/hgrc copy-pull/.hg/hgrc.bu
212 $ cp copy-pull/.hg/hgrc copy-pull/.hg/hgrc.bu
211 $ echo "[web]" >> copy-pull/.hg/hgrc
213 $ echo "[web]" >> copy-pull/.hg/hgrc
212 $ echo "cacerts=$CERTSDIR/pub.pem" >> copy-pull/.hg/hgrc
214 $ echo "cacerts=$CERTSDIR/pub.pem" >> copy-pull/.hg/hgrc
213 $ hg -R copy-pull pull --traceback
215 $ hg -R copy-pull pull --traceback
214 pulling from https://localhost:$HGPORT/
216 pulling from https://localhost:$HGPORT/
215 searching for changes
217 searching for changes
216 no changes found
218 no changes found
217 $ mv copy-pull/.hg/hgrc.bu copy-pull/.hg/hgrc
219 $ mv copy-pull/.hg/hgrc.bu copy-pull/.hg/hgrc
218
220
219 cacert configured globally, also testing expansion of environment
221 cacert configured globally, also testing expansion of environment
220 variables in the filename
222 variables in the filename
221
223
222 $ echo "[web]" >> $HGRCPATH
224 $ echo "[web]" >> $HGRCPATH
223 $ echo 'cacerts=$P/pub.pem' >> $HGRCPATH
225 $ echo 'cacerts=$P/pub.pem' >> $HGRCPATH
224 $ P="$CERTSDIR" hg -R copy-pull pull
226 $ P="$CERTSDIR" hg -R copy-pull pull
225 pulling from https://localhost:$HGPORT/
227 pulling from https://localhost:$HGPORT/
226 searching for changes
228 searching for changes
227 no changes found
229 no changes found
228 $ P="$CERTSDIR" hg -R copy-pull pull --insecure
230 $ P="$CERTSDIR" hg -R copy-pull pull --insecure
229 pulling from https://localhost:$HGPORT/
231 pulling from https://localhost:$HGPORT/
230 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
232 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
231 searching for changes
233 searching for changes
232 no changes found
234 no changes found
233
235
234 empty cacert file
236 empty cacert file
235
237
236 $ touch emptycafile
238 $ touch emptycafile
237
239
238 #if sslcontext
240 #if sslcontext
239 $ hg --config web.cacerts=emptycafile -R copy-pull pull
241 $ hg --config web.cacerts=emptycafile -R copy-pull pull
240 pulling from https://localhost:$HGPORT/
242 pulling from https://localhost:$HGPORT/
241 abort: error loading CA file emptycafile: * (glob)
243 abort: error loading CA file emptycafile: * (glob)
242 (file is empty or malformed?)
244 (file is empty or malformed?)
243 [255]
245 [255]
244 #else
246 #else
245 $ hg --config web.cacerts=emptycafile -R copy-pull pull
247 $ hg --config web.cacerts=emptycafile -R copy-pull pull
246 pulling from https://localhost:$HGPORT/
248 pulling from https://localhost:$HGPORT/
247 abort: error: * (glob)
249 abort: error: * (glob)
248 [255]
250 [255]
249 #endif
251 #endif
250
252
251 cacert mismatch
253 cacert mismatch
252
254
253 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub.pem" \
255 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub.pem" \
254 > https://127.0.0.1:$HGPORT/
256 > https://127.0.0.1:$HGPORT/
255 pulling from https://127.0.0.1:$HGPORT/
257 pulling from https://127.0.0.1:$HGPORT/
256 abort: 127.0.0.1 certificate error: certificate is for localhost
258 abort: 127.0.0.1 certificate error: certificate is for localhost
257 (set hostsecurity.127.0.0.1:certfingerprints=sha256:62:09:97:2f:97:60:e3:65:8f:12:5d:78:9e:35:a1:36:7a:65:4b:0e:9f:ac:db:c3:bc:6e:b6:a3:c0:16:e0:30 config setting or use --insecure to connect insecurely)
259 (set hostsecurity.127.0.0.1:certfingerprints=sha256:62:09:97:2f:97:60:e3:65:8f:12:5d:78:9e:35:a1:36:7a:65:4b:0e:9f:ac:db:c3:bc:6e:b6:a3:c0:16:e0:30 config setting or use --insecure to connect insecurely)
258 [255]
260 [255]
259 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub.pem" \
261 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub.pem" \
260 > https://127.0.0.1:$HGPORT/ --insecure
262 > https://127.0.0.1:$HGPORT/ --insecure
261 pulling from https://127.0.0.1:$HGPORT/
263 pulling from https://127.0.0.1:$HGPORT/
262 warning: connection security to 127.0.0.1 is disabled per current settings; communication is susceptible to eavesdropping and tampering
264 warning: connection security to 127.0.0.1 is disabled per current settings; communication is susceptible to eavesdropping and tampering
263 searching for changes
265 searching for changes
264 no changes found
266 no changes found
265 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-other.pem"
267 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-other.pem"
266 pulling from https://localhost:$HGPORT/
268 pulling from https://localhost:$HGPORT/
267 abort: error: *certificate verify failed* (glob)
269 abort: error: *certificate verify failed* (glob)
268 [255]
270 [255]
269 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-other.pem" \
271 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-other.pem" \
270 > --insecure
272 > --insecure
271 pulling from https://localhost:$HGPORT/
273 pulling from https://localhost:$HGPORT/
272 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
274 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
273 searching for changes
275 searching for changes
274 no changes found
276 no changes found
275
277
276 Test server cert which isn't valid yet
278 Test server cert which isn't valid yet
277
279
278 $ hg serve -R test -p $HGPORT1 -d --pid-file=hg1.pid --certificate=server-not-yet.pem
280 $ hg serve -R test -p $HGPORT1 -d --pid-file=hg1.pid --certificate=server-not-yet.pem
279 $ cat hg1.pid >> $DAEMON_PIDS
281 $ cat hg1.pid >> $DAEMON_PIDS
280 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-not-yet.pem" \
282 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-not-yet.pem" \
281 > https://localhost:$HGPORT1/
283 > https://localhost:$HGPORT1/
282 pulling from https://localhost:$HGPORT1/
284 pulling from https://localhost:$HGPORT1/
283 abort: error: *certificate verify failed* (glob)
285 abort: error: *certificate verify failed* (glob)
284 [255]
286 [255]
285
287
286 Test server cert which no longer is valid
288 Test server cert which no longer is valid
287
289
288 $ hg serve -R test -p $HGPORT2 -d --pid-file=hg2.pid --certificate=server-expired.pem
290 $ hg serve -R test -p $HGPORT2 -d --pid-file=hg2.pid --certificate=server-expired.pem
289 $ cat hg2.pid >> $DAEMON_PIDS
291 $ cat hg2.pid >> $DAEMON_PIDS
290 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-expired.pem" \
292 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-expired.pem" \
291 > https://localhost:$HGPORT2/
293 > https://localhost:$HGPORT2/
292 pulling from https://localhost:$HGPORT2/
294 pulling from https://localhost:$HGPORT2/
293 abort: error: *certificate verify failed* (glob)
295 abort: error: *certificate verify failed* (glob)
294 [255]
296 [255]
295
297
296 Fingerprints
298 Fingerprints
297
299
298 - works without cacerts (hostkeyfingerprints)
300 - works without cacerts (hostkeyfingerprints)
299 $ hg -R copy-pull id https://localhost:$HGPORT/ --insecure --config hostfingerprints.localhost=91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca
301 $ hg -R copy-pull id https://localhost:$HGPORT/ --insecure --config hostfingerprints.localhost=91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca
300 5fed3813f7f5
302 5fed3813f7f5
301
303
302 - works without cacerts (hostsecurity)
304 - works without cacerts (hostsecurity)
303 $ hg -R copy-pull id https://localhost:$HGPORT/ --config hostsecurity.localhost:fingerprints=sha1:914f1aff87249c09b6859b88b1906d30756491ca
305 $ hg -R copy-pull id https://localhost:$HGPORT/ --config hostsecurity.localhost:fingerprints=sha1:914f1aff87249c09b6859b88b1906d30756491ca
304 5fed3813f7f5
306 5fed3813f7f5
305
307
306 $ hg -R copy-pull id https://localhost:$HGPORT/ --config hostsecurity.localhost:fingerprints=sha256:62:09:97:2f:97:60:e3:65:8f:12:5d:78:9e:35:a1:36:7a:65:4b:0e:9f:ac:db:c3:bc:6e:b6:a3:c0:16:e0:30
308 $ hg -R copy-pull id https://localhost:$HGPORT/ --config hostsecurity.localhost:fingerprints=sha256:62:09:97:2f:97:60:e3:65:8f:12:5d:78:9e:35:a1:36:7a:65:4b:0e:9f:ac:db:c3:bc:6e:b6:a3:c0:16:e0:30
307 5fed3813f7f5
309 5fed3813f7f5
308
310
309 - multiple fingerprints specified and first matches
311 - multiple fingerprints specified and first matches
310 $ hg --config 'hostfingerprints.localhost=914f1aff87249c09b6859b88b1906d30756491ca, deadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/ --insecure
312 $ hg --config 'hostfingerprints.localhost=914f1aff87249c09b6859b88b1906d30756491ca, deadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/ --insecure
311 5fed3813f7f5
313 5fed3813f7f5
312
314
313 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:914f1aff87249c09b6859b88b1906d30756491ca, sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/
315 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:914f1aff87249c09b6859b88b1906d30756491ca, sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/
314 5fed3813f7f5
316 5fed3813f7f5
315
317
316 - multiple fingerprints specified and last matches
318 - multiple fingerprints specified and last matches
317 $ hg --config 'hostfingerprints.localhost=deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, 914f1aff87249c09b6859b88b1906d30756491ca' -R copy-pull id https://localhost:$HGPORT/ --insecure
319 $ hg --config 'hostfingerprints.localhost=deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, 914f1aff87249c09b6859b88b1906d30756491ca' -R copy-pull id https://localhost:$HGPORT/ --insecure
318 5fed3813f7f5
320 5fed3813f7f5
319
321
320 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, sha1:914f1aff87249c09b6859b88b1906d30756491ca' -R copy-pull id https://localhost:$HGPORT/
322 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, sha1:914f1aff87249c09b6859b88b1906d30756491ca' -R copy-pull id https://localhost:$HGPORT/
321 5fed3813f7f5
323 5fed3813f7f5
322
324
323 - multiple fingerprints specified and none match
325 - multiple fingerprints specified and none match
324
326
325 $ hg --config 'hostfingerprints.localhost=deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, aeadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/ --insecure
327 $ hg --config 'hostfingerprints.localhost=deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, aeadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/ --insecure
326 abort: certificate for localhost has unexpected fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca
328 abort: certificate for localhost has unexpected fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca
327 (check hostfingerprint configuration)
329 (check hostfingerprint configuration)
328 [255]
330 [255]
329
331
330 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, sha1:aeadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/
332 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, sha1:aeadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/
331 abort: certificate for localhost has unexpected fingerprint sha1:91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca
333 abort: certificate for localhost has unexpected fingerprint sha1:91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca
332 (check hostsecurity configuration)
334 (check hostsecurity configuration)
333 [255]
335 [255]
334
336
335 - fails when cert doesn't match hostname (port is ignored)
337 - fails when cert doesn't match hostname (port is ignored)
336 $ hg -R copy-pull id https://localhost:$HGPORT1/ --config hostfingerprints.localhost=914f1aff87249c09b6859b88b1906d30756491ca
338 $ hg -R copy-pull id https://localhost:$HGPORT1/ --config hostfingerprints.localhost=914f1aff87249c09b6859b88b1906d30756491ca
337 abort: certificate for localhost has unexpected fingerprint 28:ff:71:bf:65:31:14:23:ad:62:92:b4:0e:31:99:18:fc:83:e3:9b
339 abort: certificate for localhost has unexpected fingerprint 28:ff:71:bf:65:31:14:23:ad:62:92:b4:0e:31:99:18:fc:83:e3:9b
338 (check hostfingerprint configuration)
340 (check hostfingerprint configuration)
339 [255]
341 [255]
340
342
341
343
342 - ignores that certificate doesn't match hostname
344 - ignores that certificate doesn't match hostname
343 $ hg -R copy-pull id https://127.0.0.1:$HGPORT/ --config hostfingerprints.127.0.0.1=914f1aff87249c09b6859b88b1906d30756491ca
345 $ hg -R copy-pull id https://127.0.0.1:$HGPORT/ --config hostfingerprints.127.0.0.1=914f1aff87249c09b6859b88b1906d30756491ca
344 5fed3813f7f5
346 5fed3813f7f5
345
347
346 HGPORT1 is reused below for tinyproxy tests. Kill that server.
348 HGPORT1 is reused below for tinyproxy tests. Kill that server.
347 $ killdaemons.py hg1.pid
349 $ killdaemons.py hg1.pid
348
350
349 Prepare for connecting through proxy
351 Prepare for connecting through proxy
350
352
351 $ tinyproxy.py $HGPORT1 localhost >proxy.log </dev/null 2>&1 &
353 $ tinyproxy.py $HGPORT1 localhost >proxy.log </dev/null 2>&1 &
352 $ while [ ! -f proxy.pid ]; do sleep 0; done
354 $ while [ ! -f proxy.pid ]; do sleep 0; done
353 $ cat proxy.pid >> $DAEMON_PIDS
355 $ cat proxy.pid >> $DAEMON_PIDS
354
356
355 $ echo "[http_proxy]" >> copy-pull/.hg/hgrc
357 $ echo "[http_proxy]" >> copy-pull/.hg/hgrc
356 $ echo "always=True" >> copy-pull/.hg/hgrc
358 $ echo "always=True" >> copy-pull/.hg/hgrc
357 $ echo "[hostfingerprints]" >> copy-pull/.hg/hgrc
359 $ echo "[hostfingerprints]" >> copy-pull/.hg/hgrc
358 $ echo "localhost =" >> copy-pull/.hg/hgrc
360 $ echo "localhost =" >> copy-pull/.hg/hgrc
359
361
360 Test unvalidated https through proxy
362 Test unvalidated https through proxy
361
363
362 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --insecure --traceback
364 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --insecure --traceback
363 pulling from https://localhost:$HGPORT/
365 pulling from https://localhost:$HGPORT/
364 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
366 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
365 searching for changes
367 searching for changes
366 no changes found
368 no changes found
367
369
368 Test https with cacert and fingerprint through proxy
370 Test https with cacert and fingerprint through proxy
369
371
370 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull \
372 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull \
371 > --config web.cacerts="$CERTSDIR/pub.pem"
373 > --config web.cacerts="$CERTSDIR/pub.pem"
372 pulling from https://localhost:$HGPORT/
374 pulling from https://localhost:$HGPORT/
373 searching for changes
375 searching for changes
374 no changes found
376 no changes found
375 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull https://127.0.0.1:$HGPORT/ --config hostfingerprints.127.0.0.1=914f1aff87249c09b6859b88b1906d30756491ca
377 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull https://127.0.0.1:$HGPORT/ --config hostfingerprints.127.0.0.1=914f1aff87249c09b6859b88b1906d30756491ca
376 pulling from https://127.0.0.1:$HGPORT/
378 pulling from https://127.0.0.1:$HGPORT/
377 searching for changes
379 searching for changes
378 no changes found
380 no changes found
379
381
380 Test https with cert problems through proxy
382 Test https with cert problems through proxy
381
383
382 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull \
384 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull \
383 > --config web.cacerts="$CERTSDIR/pub-other.pem"
385 > --config web.cacerts="$CERTSDIR/pub-other.pem"
384 pulling from https://localhost:$HGPORT/
386 pulling from https://localhost:$HGPORT/
385 abort: error: *certificate verify failed* (glob)
387 abort: error: *certificate verify failed* (glob)
386 [255]
388 [255]
387 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull \
389 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull \
388 > --config web.cacerts="$CERTSDIR/pub-expired.pem" https://localhost:$HGPORT2/
390 > --config web.cacerts="$CERTSDIR/pub-expired.pem" https://localhost:$HGPORT2/
389 pulling from https://localhost:$HGPORT2/
391 pulling from https://localhost:$HGPORT2/
390 abort: error: *certificate verify failed* (glob)
392 abort: error: *certificate verify failed* (glob)
391 [255]
393 [255]
392
394
393
395
394 $ killdaemons.py hg0.pid
396 $ killdaemons.py hg0.pid
395
397
396 #if sslcontext
398 #if sslcontext
397
399
398 Start patched hgweb that requires client certificates:
400 Start patched hgweb that requires client certificates:
399
401
400 $ cat << EOT > reqclientcert.py
402 $ cat << EOT > reqclientcert.py
401 > import ssl
403 > import ssl
402 > from mercurial.hgweb import server
404 > from mercurial.hgweb import server
403 > class _httprequesthandlersslclientcert(server._httprequesthandlerssl):
405 > class _httprequesthandlersslclientcert(server._httprequesthandlerssl):
404 > @staticmethod
406 > @staticmethod
405 > def preparehttpserver(httpserver, ssl_cert):
407 > def preparehttpserver(httpserver, ssl_cert):
406 > sslcontext = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
408 > sslcontext = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
407 > sslcontext.verify_mode = ssl.CERT_REQUIRED
409 > sslcontext.verify_mode = ssl.CERT_REQUIRED
408 > sslcontext.load_cert_chain(ssl_cert)
410 > sslcontext.load_cert_chain(ssl_cert)
409 > # verify clients by server certificate
411 > # verify clients by server certificate
410 > sslcontext.load_verify_locations(ssl_cert)
412 > sslcontext.load_verify_locations(ssl_cert)
411 > httpserver.socket = sslcontext.wrap_socket(httpserver.socket,
413 > httpserver.socket = sslcontext.wrap_socket(httpserver.socket,
412 > server_side=True)
414 > server_side=True)
413 > server._httprequesthandlerssl = _httprequesthandlersslclientcert
415 > server._httprequesthandlerssl = _httprequesthandlersslclientcert
414 > EOT
416 > EOT
415 $ cd test
417 $ cd test
416 $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV \
418 $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV \
417 > --config extensions.reqclientcert=../reqclientcert.py
419 > --config extensions.reqclientcert=../reqclientcert.py
418 $ cat ../hg0.pid >> $DAEMON_PIDS
420 $ cat ../hg0.pid >> $DAEMON_PIDS
419 $ cd ..
421 $ cd ..
420
422
421 without client certificate:
423 without client certificate:
422
424
423 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/
425 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/
424 abort: error: *handshake failure* (glob)
426 abort: error: *handshake failure* (glob)
425 [255]
427 [255]
426
428
427 with client certificate:
429 with client certificate:
428
430
429 $ cat << EOT >> $HGRCPATH
431 $ cat << EOT >> $HGRCPATH
430 > [auth]
432 > [auth]
431 > l.prefix = localhost
433 > l.prefix = localhost
432 > l.cert = $CERTSDIR/client-cert.pem
434 > l.cert = $CERTSDIR/client-cert.pem
433 > l.key = $CERTSDIR/client-key.pem
435 > l.key = $CERTSDIR/client-key.pem
434 > EOT
436 > EOT
435
437
436 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
438 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
437 > --config auth.l.key="$CERTSDIR/client-key-decrypted.pem"
439 > --config auth.l.key="$CERTSDIR/client-key-decrypted.pem"
438 5fed3813f7f5
440 5fed3813f7f5
439
441
440 $ printf '1234\n' | env P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
442 $ printf '1234\n' | env P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
441 > --config ui.interactive=True --config ui.nontty=True
443 > --config ui.interactive=True --config ui.nontty=True
442 passphrase for */client-key.pem: 5fed3813f7f5 (glob)
444 passphrase for */client-key.pem: 5fed3813f7f5 (glob)
443
445
444 $ env P="$CERTSDIR" hg id https://localhost:$HGPORT/
446 $ env P="$CERTSDIR" hg id https://localhost:$HGPORT/
445 abort: error: * (glob)
447 abort: error: * (glob)
446 [255]
448 [255]
447
449
448 #endif
450 #endif
@@ -1,121 +1,123
1 #require serve ssl
1 #require serve ssl
2
2
3 Set up SMTP server:
3 Set up SMTP server:
4
4
5 $ CERTSDIR="$TESTDIR/sslcerts"
5 $ CERTSDIR="$TESTDIR/sslcerts"
6 $ cat "$CERTSDIR/priv.pem" "$CERTSDIR/pub.pem" >> server.pem
6 $ cat "$CERTSDIR/priv.pem" "$CERTSDIR/pub.pem" >> server.pem
7
7
8 $ python "$TESTDIR/dummysmtpd.py" -p $HGPORT --pid-file a.pid -d \
8 $ python "$TESTDIR/dummysmtpd.py" -p $HGPORT --pid-file a.pid -d \
9 > --tls smtps --certificate `pwd`/server.pem
9 > --tls smtps --certificate `pwd`/server.pem
10 listening at localhost:$HGPORT
10 listening at localhost:$HGPORT
11 $ cat a.pid >> $DAEMON_PIDS
11 $ cat a.pid >> $DAEMON_PIDS
12
12
13 Ensure hg email output is sent to stdout:
13 Ensure hg email output is sent to stdout:
14
14
15 $ unset PAGER
15 $ unset PAGER
16
16
17 Set up repository:
17 Set up repository:
18
18
19 $ hg init t
19 $ hg init t
20 $ cd t
20 $ cd t
21 $ cat <<EOF >> .hg/hgrc
21 $ cat <<EOF >> .hg/hgrc
22 > [extensions]
22 > [extensions]
23 > patchbomb =
23 > patchbomb =
24 > [email]
24 > [email]
25 > method = smtp
25 > method = smtp
26 > [smtp]
26 > [smtp]
27 > host = localhost
27 > host = localhost
28 > port = $HGPORT
28 > port = $HGPORT
29 > tls = smtps
29 > tls = smtps
30 > EOF
30 > EOF
31
31
32 $ echo a > a
32 $ echo a > a
33 $ hg commit -Ama -d '1 0'
33 $ hg commit -Ama -d '1 0'
34 adding a
34 adding a
35
35
36 Utility functions:
36 Utility functions:
37
37
38 $ DISABLECACERTS=
38 $ DISABLECACERTS=
39 $ try () {
39 $ try () {
40 > hg email $DISABLECACERTS -f quux -t foo -c bar -r tip "$@"
40 > hg email $DISABLECACERTS -f quux -t foo -c bar -r tip "$@"
41 > }
41 > }
42
42
43 Our test cert is not signed by a trusted CA. It should fail to verify if
43 Our test cert is not signed by a trusted CA. It should fail to verify if
44 we are able to load CA certs:
44 we are able to load CA certs:
45
45
46 #if sslcontext defaultcacerts no-defaultcacertsloaded
46 #if sslcontext defaultcacerts no-defaultcacertsloaded
47 $ try
47 $ try
48 this patch series consists of 1 patches.
48 this patch series consists of 1 patches.
49
49
50
50
51 (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)
51 (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)
52 (?i)abort: .*?certificate.verify.failed.* (re)
52 (?i)abort: .*?certificate.verify.failed.* (re)
53 [255]
53 [255]
54 #endif
54 #endif
55
55
56 #if no-sslcontext defaultcacerts
56 #if no-sslcontext defaultcacerts
57 $ try
57 $ try
58 this patch series consists of 1 patches.
58 this patch series consists of 1 patches.
59
59
60
60
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 (?i)abort: .*?certificate.verify.failed.* (re)
62 (?i)abort: .*?certificate.verify.failed.* (re)
62 [255]
63 [255]
63 #endif
64 #endif
64
65
65 #if defaultcacertsloaded
66 #if defaultcacertsloaded
66 $ try
67 $ try
67 this patch series consists of 1 patches.
68 this patch series consists of 1 patches.
68
69
69
70
71 (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) (?)
70 (?i)abort: .*?certificate.verify.failed.* (re)
72 (?i)abort: .*?certificate.verify.failed.* (re)
71 [255]
73 [255]
72
74
73 #endif
75 #endif
74
76
75 #if no-defaultcacerts
77 #if no-defaultcacerts
76 $ try
78 $ try
77 this patch series consists of 1 patches.
79 this patch series consists of 1 patches.
78
80
79
81
80 (unable to load * certificates; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message) (glob) (?)
82 (unable to load * certificates; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message) (glob) (?)
81 abort: localhost certificate error: no certificate received
83 abort: localhost certificate error: no certificate received
82 (set hostsecurity.localhost:certfingerprints=sha256:62:09:97:2f:97:60:e3:65:8f:12:5d:78:9e:35:a1:36:7a:65:4b:0e:9f:ac:db:c3:bc:6e:b6:a3:c0:16:e0:30 config setting or use --insecure to connect insecurely)
84 (set hostsecurity.localhost:certfingerprints=sha256:62:09:97:2f:97:60:e3:65:8f:12:5d:78:9e:35:a1:36:7a:65:4b:0e:9f:ac:db:c3:bc:6e:b6:a3:c0:16:e0:30 config setting or use --insecure to connect insecurely)
83 [255]
85 [255]
84 #endif
86 #endif
85
87
86 $ DISABLECACERTS="--config devel.disableloaddefaultcerts=true"
88 $ DISABLECACERTS="--config devel.disableloaddefaultcerts=true"
87
89
88 Without certificates:
90 Without certificates:
89
91
90 $ try --debug
92 $ try --debug
91 this patch series consists of 1 patches.
93 this patch series consists of 1 patches.
92
94
93
95
94 (using smtps)
96 (using smtps)
95 sending mail: smtp host localhost, port * (glob)
97 sending mail: smtp host localhost, port * (glob)
96 (verifying remote certificate)
98 (verifying remote certificate)
97 abort: unable to verify security of localhost (no loaded CA certificates); refusing to connect
99 abort: unable to verify security of localhost (no loaded CA certificates); refusing to connect
98 (see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error or set hostsecurity.localhost:fingerprints=sha256:62:09:97:2f:97:60:e3:65:8f:12:5d:78:9e:35:a1:36:7a:65:4b:0e:9f:ac:db:c3:bc:6e:b6:a3:c0:16:e0:30 to trust this server)
100 (see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error or set hostsecurity.localhost:fingerprints=sha256:62:09:97:2f:97:60:e3:65:8f:12:5d:78:9e:35:a1:36:7a:65:4b:0e:9f:ac:db:c3:bc:6e:b6:a3:c0:16:e0:30 to trust this server)
99 [255]
101 [255]
100
102
101 With global certificates:
103 With global certificates:
102
104
103 $ try --debug --config web.cacerts="$CERTSDIR/pub.pem"
105 $ try --debug --config web.cacerts="$CERTSDIR/pub.pem"
104 this patch series consists of 1 patches.
106 this patch series consists of 1 patches.
105
107
106
108
107 (using smtps)
109 (using smtps)
108 sending mail: smtp host localhost, port * (glob)
110 sending mail: smtp host localhost, port * (glob)
109 (verifying remote certificate)
111 (verifying remote certificate)
110 sending [PATCH] a ...
112 sending [PATCH] a ...
111
113
112 With invalid certificates:
114 With invalid certificates:
113
115
114 $ try --config web.cacerts="$CERTSDIR/pub-other.pem"
116 $ try --config web.cacerts="$CERTSDIR/pub-other.pem"
115 this patch series consists of 1 patches.
117 this patch series consists of 1 patches.
116
118
117
119
118 (?i)abort: .*?certificate.verify.failed.* (re)
120 (?i)abort: .*?certificate.verify.failed.* (re)
119 [255]
121 [255]
120
122
121 $ cd ..
123 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now