##// END OF EJS Templates
sslutil: abort when unable to verify peer connection (BC)...
Gregory Szorc -
r29411:e1778b9c default
parent child Browse files
Show More
@@ -1,428 +1,434
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 ssl
14 import ssl
15 import sys
15 import sys
16
16
17 from .i18n import _
17 from .i18n import _
18 from . import (
18 from . import (
19 error,
19 error,
20 util,
20 util,
21 )
21 )
22
22
23 # Python 2.7.9+ overhauled the built-in SSL/TLS features of Python. It added
23 # Python 2.7.9+ overhauled the built-in SSL/TLS features of Python. It added
24 # support for TLS 1.1, TLS 1.2, SNI, system CA stores, etc. These features are
24 # support for TLS 1.1, TLS 1.2, SNI, system CA stores, etc. These features are
25 # all exposed via the "ssl" module.
25 # all exposed via the "ssl" module.
26 #
26 #
27 # Depending on the version of Python being used, SSL/TLS support is either
27 # Depending on the version of Python being used, SSL/TLS support is either
28 # modern/secure or legacy/insecure. Many operations in this module have
28 # modern/secure or legacy/insecure. Many operations in this module have
29 # separate code paths depending on support in Python.
29 # separate code paths depending on support in Python.
30
30
31 hassni = getattr(ssl, 'HAS_SNI', False)
31 hassni = getattr(ssl, 'HAS_SNI', False)
32
32
33 try:
33 try:
34 OP_NO_SSLv2 = ssl.OP_NO_SSLv2
34 OP_NO_SSLv2 = ssl.OP_NO_SSLv2
35 OP_NO_SSLv3 = ssl.OP_NO_SSLv3
35 OP_NO_SSLv3 = ssl.OP_NO_SSLv3
36 except AttributeError:
36 except AttributeError:
37 OP_NO_SSLv2 = 0x1000000
37 OP_NO_SSLv2 = 0x1000000
38 OP_NO_SSLv3 = 0x2000000
38 OP_NO_SSLv3 = 0x2000000
39
39
40 try:
40 try:
41 # ssl.SSLContext was added in 2.7.9 and presence indicates modern
41 # ssl.SSLContext was added in 2.7.9 and presence indicates modern
42 # SSL/TLS features are available.
42 # SSL/TLS features are available.
43 SSLContext = ssl.SSLContext
43 SSLContext = ssl.SSLContext
44 modernssl = True
44 modernssl = True
45 _canloaddefaultcerts = util.safehasattr(SSLContext, 'load_default_certs')
45 _canloaddefaultcerts = util.safehasattr(SSLContext, 'load_default_certs')
46 except AttributeError:
46 except AttributeError:
47 modernssl = False
47 modernssl = False
48 _canloaddefaultcerts = False
48 _canloaddefaultcerts = False
49
49
50 # We implement SSLContext using the interface from the standard library.
50 # We implement SSLContext using the interface from the standard library.
51 class SSLContext(object):
51 class SSLContext(object):
52 # ssl.wrap_socket gained the "ciphers" named argument in 2.7.
52 # ssl.wrap_socket gained the "ciphers" named argument in 2.7.
53 _supportsciphers = sys.version_info >= (2, 7)
53 _supportsciphers = sys.version_info >= (2, 7)
54
54
55 def __init__(self, protocol):
55 def __init__(self, protocol):
56 # From the public interface of SSLContext
56 # From the public interface of SSLContext
57 self.protocol = protocol
57 self.protocol = protocol
58 self.check_hostname = False
58 self.check_hostname = False
59 self.options = 0
59 self.options = 0
60 self.verify_mode = ssl.CERT_NONE
60 self.verify_mode = ssl.CERT_NONE
61
61
62 # Used by our implementation.
62 # Used by our implementation.
63 self._certfile = None
63 self._certfile = None
64 self._keyfile = None
64 self._keyfile = None
65 self._certpassword = None
65 self._certpassword = None
66 self._cacerts = None
66 self._cacerts = None
67 self._ciphers = None
67 self._ciphers = None
68
68
69 def load_cert_chain(self, certfile, keyfile=None, password=None):
69 def load_cert_chain(self, certfile, keyfile=None, password=None):
70 self._certfile = certfile
70 self._certfile = certfile
71 self._keyfile = keyfile
71 self._keyfile = keyfile
72 self._certpassword = password
72 self._certpassword = password
73
73
74 def load_default_certs(self, purpose=None):
74 def load_default_certs(self, purpose=None):
75 pass
75 pass
76
76
77 def load_verify_locations(self, cafile=None, capath=None, cadata=None):
77 def load_verify_locations(self, cafile=None, capath=None, cadata=None):
78 if capath:
78 if capath:
79 raise error.Abort(_('capath not supported'))
79 raise error.Abort(_('capath not supported'))
80 if cadata:
80 if cadata:
81 raise error.Abort(_('cadata not supported'))
81 raise error.Abort(_('cadata not supported'))
82
82
83 self._cacerts = cafile
83 self._cacerts = cafile
84
84
85 def set_ciphers(self, ciphers):
85 def set_ciphers(self, ciphers):
86 if not self._supportsciphers:
86 if not self._supportsciphers:
87 raise error.Abort(_('setting ciphers not supported'))
87 raise error.Abort(_('setting ciphers not supported'))
88
88
89 self._ciphers = ciphers
89 self._ciphers = ciphers
90
90
91 def wrap_socket(self, socket, server_hostname=None, server_side=False):
91 def wrap_socket(self, socket, server_hostname=None, server_side=False):
92 # server_hostname is unique to SSLContext.wrap_socket and is used
92 # server_hostname is unique to SSLContext.wrap_socket and is used
93 # for SNI in that context. So there's nothing for us to do with it
93 # for SNI in that context. So there's nothing for us to do with it
94 # in this legacy code since we don't support SNI.
94 # in this legacy code since we don't support SNI.
95
95
96 args = {
96 args = {
97 'keyfile': self._keyfile,
97 'keyfile': self._keyfile,
98 'certfile': self._certfile,
98 'certfile': self._certfile,
99 'server_side': server_side,
99 'server_side': server_side,
100 'cert_reqs': self.verify_mode,
100 'cert_reqs': self.verify_mode,
101 'ssl_version': self.protocol,
101 'ssl_version': self.protocol,
102 'ca_certs': self._cacerts,
102 'ca_certs': self._cacerts,
103 }
103 }
104
104
105 if self._supportsciphers:
105 if self._supportsciphers:
106 args['ciphers'] = self._ciphers
106 args['ciphers'] = self._ciphers
107
107
108 return ssl.wrap_socket(socket, **args)
108 return ssl.wrap_socket(socket, **args)
109
109
110 def _hostsettings(ui, hostname):
110 def _hostsettings(ui, hostname):
111 """Obtain security settings for a hostname.
111 """Obtain security settings for a hostname.
112
112
113 Returns a dict of settings relevant to that hostname.
113 Returns a dict of settings relevant to that hostname.
114 """
114 """
115 s = {
115 s = {
116 # Whether we should attempt to load default/available CA certs
116 # Whether we should attempt to load default/available CA certs
117 # if an explicit ``cafile`` is not defined.
117 # if an explicit ``cafile`` is not defined.
118 'allowloaddefaultcerts': True,
118 'allowloaddefaultcerts': True,
119 # List of 2-tuple of (hash algorithm, hash).
119 # List of 2-tuple of (hash algorithm, hash).
120 'certfingerprints': [],
120 'certfingerprints': [],
121 # Path to file containing concatenated CA certs. Used by
121 # Path to file containing concatenated CA certs. Used by
122 # SSLContext.load_verify_locations().
122 # SSLContext.load_verify_locations().
123 'cafile': None,
123 'cafile': None,
124 # Whether certificate verification should be disabled.
124 # Whether certificate verification should be disabled.
125 'disablecertverification': False,
125 'disablecertverification': False,
126 # Whether the legacy [hostfingerprints] section has data for this host.
126 # Whether the legacy [hostfingerprints] section has data for this host.
127 'legacyfingerprint': False,
127 'legacyfingerprint': False,
128 # ssl.CERT_* constant used by SSLContext.verify_mode.
128 # ssl.CERT_* constant used by SSLContext.verify_mode.
129 'verifymode': None,
129 'verifymode': None,
130 }
130 }
131
131
132 # Look for fingerprints in [hostsecurity] section. Value is a list
132 # Look for fingerprints in [hostsecurity] section. Value is a list
133 # of <alg>:<fingerprint> strings.
133 # of <alg>:<fingerprint> strings.
134 fingerprints = ui.configlist('hostsecurity', '%s:fingerprints' % hostname,
134 fingerprints = ui.configlist('hostsecurity', '%s:fingerprints' % hostname,
135 [])
135 [])
136 for fingerprint in fingerprints:
136 for fingerprint in fingerprints:
137 if not (fingerprint.startswith(('sha1:', 'sha256:', 'sha512:'))):
137 if not (fingerprint.startswith(('sha1:', 'sha256:', 'sha512:'))):
138 raise error.Abort(_('invalid fingerprint for %s: %s') % (
138 raise error.Abort(_('invalid fingerprint for %s: %s') % (
139 hostname, fingerprint),
139 hostname, fingerprint),
140 hint=_('must begin with "sha1:", "sha256:", '
140 hint=_('must begin with "sha1:", "sha256:", '
141 'or "sha512:"'))
141 'or "sha512:"'))
142
142
143 alg, fingerprint = fingerprint.split(':', 1)
143 alg, fingerprint = fingerprint.split(':', 1)
144 fingerprint = fingerprint.replace(':', '').lower()
144 fingerprint = fingerprint.replace(':', '').lower()
145 s['certfingerprints'].append((alg, fingerprint))
145 s['certfingerprints'].append((alg, fingerprint))
146
146
147 # Fingerprints from [hostfingerprints] are always SHA-1.
147 # Fingerprints from [hostfingerprints] are always SHA-1.
148 for fingerprint in ui.configlist('hostfingerprints', hostname, []):
148 for fingerprint in ui.configlist('hostfingerprints', hostname, []):
149 fingerprint = fingerprint.replace(':', '').lower()
149 fingerprint = fingerprint.replace(':', '').lower()
150 s['certfingerprints'].append(('sha1', fingerprint))
150 s['certfingerprints'].append(('sha1', fingerprint))
151 s['legacyfingerprint'] = True
151 s['legacyfingerprint'] = True
152
152
153 # If a host cert fingerprint is defined, it is the only thing that
153 # If a host cert fingerprint is defined, it is the only thing that
154 # matters. No need to validate CA certs.
154 # matters. No need to validate CA certs.
155 if s['certfingerprints']:
155 if s['certfingerprints']:
156 s['verifymode'] = ssl.CERT_NONE
156 s['verifymode'] = ssl.CERT_NONE
157
157
158 # If --insecure is used, don't take CAs into consideration.
158 # If --insecure is used, don't take CAs into consideration.
159 elif ui.insecureconnections:
159 elif ui.insecureconnections:
160 s['disablecertverification'] = True
160 s['disablecertverification'] = True
161 s['verifymode'] = ssl.CERT_NONE
161 s['verifymode'] = ssl.CERT_NONE
162
162
163 if ui.configbool('devel', 'disableloaddefaultcerts'):
163 if ui.configbool('devel', 'disableloaddefaultcerts'):
164 s['allowloaddefaultcerts'] = False
164 s['allowloaddefaultcerts'] = False
165
165
166 # If both fingerprints and a per-host ca file are specified, issue a warning
166 # If both fingerprints and a per-host ca file are specified, issue a warning
167 # because users should not be surprised about what security is or isn't
167 # because users should not be surprised about what security is or isn't
168 # being performed.
168 # being performed.
169 cafile = ui.config('hostsecurity', '%s:verifycertsfile' % hostname)
169 cafile = ui.config('hostsecurity', '%s:verifycertsfile' % hostname)
170 if s['certfingerprints'] and cafile:
170 if s['certfingerprints'] and cafile:
171 ui.warn(_('(hostsecurity.%s:verifycertsfile ignored when host '
171 ui.warn(_('(hostsecurity.%s:verifycertsfile ignored when host '
172 'fingerprints defined; using host fingerprints for '
172 'fingerprints defined; using host fingerprints for '
173 'verification)\n') % hostname)
173 'verification)\n') % hostname)
174
174
175 # Try to hook up CA certificate validation unless something above
175 # Try to hook up CA certificate validation unless something above
176 # makes it not necessary.
176 # makes it not necessary.
177 if s['verifymode'] is None:
177 if s['verifymode'] is None:
178 # Look at per-host ca file first.
178 # Look at per-host ca file first.
179 if cafile:
179 if cafile:
180 cafile = util.expandpath(cafile)
180 cafile = util.expandpath(cafile)
181 if not os.path.exists(cafile):
181 if not os.path.exists(cafile):
182 raise error.Abort(_('path specified by %s does not exist: %s') %
182 raise error.Abort(_('path specified by %s does not exist: %s') %
183 ('hostsecurity.%s:verifycertsfile' % hostname,
183 ('hostsecurity.%s:verifycertsfile' % hostname,
184 cafile))
184 cafile))
185 s['cafile'] = cafile
185 s['cafile'] = cafile
186 else:
186 else:
187 # Find global certificates file in config.
187 # Find global certificates file in config.
188 cafile = ui.config('web', 'cacerts')
188 cafile = ui.config('web', 'cacerts')
189
189
190 if cafile:
190 if cafile:
191 cafile = util.expandpath(cafile)
191 cafile = util.expandpath(cafile)
192 if not os.path.exists(cafile):
192 if not os.path.exists(cafile):
193 raise error.Abort(_('could not find web.cacerts: %s') %
193 raise error.Abort(_('could not find web.cacerts: %s') %
194 cafile)
194 cafile)
195 else:
195 else:
196 # No global CA certs. See if we can load defaults.
196 # No global CA certs. See if we can load defaults.
197 cafile = _defaultcacerts()
197 cafile = _defaultcacerts()
198 if cafile:
198 if cafile:
199 ui.debug('using %s to enable OS X system CA\n' % cafile)
199 ui.debug('using %s to enable OS X system CA\n' % cafile)
200
200
201 s['cafile'] = cafile
201 s['cafile'] = cafile
202
202
203 # Require certificate validation if CA certs are being loaded and
203 # Require certificate validation if CA certs are being loaded and
204 # verification hasn't been disabled above.
204 # verification hasn't been disabled above.
205 if cafile or (_canloaddefaultcerts and s['allowloaddefaultcerts']):
205 if cafile or (_canloaddefaultcerts and s['allowloaddefaultcerts']):
206 s['verifymode'] = ssl.CERT_REQUIRED
206 s['verifymode'] = ssl.CERT_REQUIRED
207 else:
207 else:
208 # At this point we don't have a fingerprint, aren't being
208 # At this point we don't have a fingerprint, aren't being
209 # explicitly insecure, and can't load CA certs. Connecting
209 # explicitly insecure, and can't load CA certs. Connecting
210 # at this point is insecure. But we do it for BC reasons.
210 # is insecure. We allow the connection and abort during
211 # TODO abort here to make secure by default.
211 # validation (once we have the fingerprint to print to the
212 # user).
212 s['verifymode'] = ssl.CERT_NONE
213 s['verifymode'] = ssl.CERT_NONE
213
214
214 assert s['verifymode'] is not None
215 assert s['verifymode'] is not None
215
216
216 return s
217 return s
217
218
218 def wrapsocket(sock, keyfile, certfile, ui, serverhostname=None):
219 def wrapsocket(sock, keyfile, certfile, ui, serverhostname=None):
219 """Add SSL/TLS to a socket.
220 """Add SSL/TLS to a socket.
220
221
221 This is a glorified wrapper for ``ssl.wrap_socket()``. It makes sane
222 This is a glorified wrapper for ``ssl.wrap_socket()``. It makes sane
222 choices based on what security options are available.
223 choices based on what security options are available.
223
224
224 In addition to the arguments supported by ``ssl.wrap_socket``, we allow
225 In addition to the arguments supported by ``ssl.wrap_socket``, we allow
225 the following additional arguments:
226 the following additional arguments:
226
227
227 * serverhostname - The expected hostname of the remote server. If the
228 * serverhostname - The expected hostname of the remote server. If the
228 server (and client) support SNI, this tells the server which certificate
229 server (and client) support SNI, this tells the server which certificate
229 to use.
230 to use.
230 """
231 """
231 if not serverhostname:
232 if not serverhostname:
232 raise error.Abort(_('serverhostname argument is required'))
233 raise error.Abort(_('serverhostname argument is required'))
233
234
234 settings = _hostsettings(ui, serverhostname)
235 settings = _hostsettings(ui, serverhostname)
235
236
236 # Despite its name, PROTOCOL_SSLv23 selects the highest protocol
237 # Despite its name, PROTOCOL_SSLv23 selects the highest protocol
237 # that both ends support, including TLS protocols. On legacy stacks,
238 # that both ends support, including TLS protocols. On legacy stacks,
238 # the highest it likely goes in TLS 1.0. On modern stacks, it can
239 # the highest it likely goes in TLS 1.0. On modern stacks, it can
239 # support TLS 1.2.
240 # support TLS 1.2.
240 #
241 #
241 # The PROTOCOL_TLSv* constants select a specific TLS version
242 # The PROTOCOL_TLSv* constants select a specific TLS version
242 # only (as opposed to multiple versions). So the method for
243 # only (as opposed to multiple versions). So the method for
243 # supporting multiple TLS versions is to use PROTOCOL_SSLv23 and
244 # supporting multiple TLS versions is to use PROTOCOL_SSLv23 and
244 # disable protocols via SSLContext.options and OP_NO_* constants.
245 # disable protocols via SSLContext.options and OP_NO_* constants.
245 # However, SSLContext.options doesn't work unless we have the
246 # However, SSLContext.options doesn't work unless we have the
246 # full/real SSLContext available to us.
247 # full/real SSLContext available to us.
247 #
248 #
248 # SSLv2 and SSLv3 are broken. We ban them outright.
249 # SSLv2 and SSLv3 are broken. We ban them outright.
249 if modernssl:
250 if modernssl:
250 protocol = ssl.PROTOCOL_SSLv23
251 protocol = ssl.PROTOCOL_SSLv23
251 else:
252 else:
252 protocol = ssl.PROTOCOL_TLSv1
253 protocol = ssl.PROTOCOL_TLSv1
253
254
254 # TODO use ssl.create_default_context() on modernssl.
255 # TODO use ssl.create_default_context() on modernssl.
255 sslcontext = SSLContext(protocol)
256 sslcontext = SSLContext(protocol)
256
257
257 # This is a no-op on old Python.
258 # This is a no-op on old Python.
258 sslcontext.options |= OP_NO_SSLv2 | OP_NO_SSLv3
259 sslcontext.options |= OP_NO_SSLv2 | OP_NO_SSLv3
259
260
260 # This still works on our fake SSLContext.
261 # This still works on our fake SSLContext.
261 sslcontext.verify_mode = settings['verifymode']
262 sslcontext.verify_mode = settings['verifymode']
262
263
263 if certfile is not None:
264 if certfile is not None:
264 def password():
265 def password():
265 f = keyfile or certfile
266 f = keyfile or certfile
266 return ui.getpass(_('passphrase for %s: ') % f, '')
267 return ui.getpass(_('passphrase for %s: ') % f, '')
267 sslcontext.load_cert_chain(certfile, keyfile, password)
268 sslcontext.load_cert_chain(certfile, keyfile, password)
268
269
269 if settings['cafile'] is not None:
270 if settings['cafile'] is not None:
270 sslcontext.load_verify_locations(cafile=settings['cafile'])
271 sslcontext.load_verify_locations(cafile=settings['cafile'])
271 caloaded = True
272 caloaded = True
272 elif settings['allowloaddefaultcerts']:
273 elif settings['allowloaddefaultcerts']:
273 # This is a no-op on old Python.
274 # This is a no-op on old Python.
274 sslcontext.load_default_certs()
275 sslcontext.load_default_certs()
275 caloaded = True
276 caloaded = True
276 else:
277 else:
277 caloaded = False
278 caloaded = False
278
279
279 sslsocket = sslcontext.wrap_socket(sock, server_hostname=serverhostname)
280 sslsocket = sslcontext.wrap_socket(sock, server_hostname=serverhostname)
280 # check if wrap_socket failed silently because socket had been
281 # check if wrap_socket failed silently because socket had been
281 # closed
282 # closed
282 # - see http://bugs.python.org/issue13721
283 # - see http://bugs.python.org/issue13721
283 if not sslsocket.cipher():
284 if not sslsocket.cipher():
284 raise error.Abort(_('ssl connection failed'))
285 raise error.Abort(_('ssl connection failed'))
285
286
286 sslsocket._hgstate = {
287 sslsocket._hgstate = {
287 'caloaded': caloaded,
288 'caloaded': caloaded,
288 'hostname': serverhostname,
289 'hostname': serverhostname,
289 'settings': settings,
290 'settings': settings,
290 'ui': ui,
291 'ui': ui,
291 }
292 }
292
293
293 return sslsocket
294 return sslsocket
294
295
295 def _verifycert(cert, hostname):
296 def _verifycert(cert, hostname):
296 '''Verify that cert (in socket.getpeercert() format) matches hostname.
297 '''Verify that cert (in socket.getpeercert() format) matches hostname.
297 CRLs is not handled.
298 CRLs is not handled.
298
299
299 Returns error message if any problems are found and None on success.
300 Returns error message if any problems are found and None on success.
300 '''
301 '''
301 if not cert:
302 if not cert:
302 return _('no certificate received')
303 return _('no certificate received')
303 dnsname = hostname.lower()
304 dnsname = hostname.lower()
304 def matchdnsname(certname):
305 def matchdnsname(certname):
305 return (certname == dnsname or
306 return (certname == dnsname or
306 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1])
307 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1])
307
308
308 san = cert.get('subjectAltName', [])
309 san = cert.get('subjectAltName', [])
309 if san:
310 if san:
310 certnames = [value.lower() for key, value in san if key == 'DNS']
311 certnames = [value.lower() for key, value in san if key == 'DNS']
311 for name in certnames:
312 for name in certnames:
312 if matchdnsname(name):
313 if matchdnsname(name):
313 return None
314 return None
314 if certnames:
315 if certnames:
315 return _('certificate is for %s') % ', '.join(certnames)
316 return _('certificate is for %s') % ', '.join(certnames)
316
317
317 # subject is only checked when subjectAltName is empty
318 # subject is only checked when subjectAltName is empty
318 for s in cert.get('subject', []):
319 for s in cert.get('subject', []):
319 key, value = s[0]
320 key, value = s[0]
320 if key == 'commonName':
321 if key == 'commonName':
321 try:
322 try:
322 # 'subject' entries are unicode
323 # 'subject' entries are unicode
323 certname = value.lower().encode('ascii')
324 certname = value.lower().encode('ascii')
324 except UnicodeEncodeError:
325 except UnicodeEncodeError:
325 return _('IDN in certificate not supported')
326 return _('IDN in certificate not supported')
326 if matchdnsname(certname):
327 if matchdnsname(certname):
327 return None
328 return None
328 return _('certificate is for %s') % certname
329 return _('certificate is for %s') % certname
329 return _('no commonName or subjectAltName found in certificate')
330 return _('no commonName or subjectAltName found in certificate')
330
331
331 def _plainapplepython():
332 def _plainapplepython():
332 """return true if this seems to be a pure Apple Python that
333 """return true if this seems to be a pure Apple Python that
333 * is unfrozen and presumably has the whole mercurial module in the file
334 * is unfrozen and presumably has the whole mercurial module in the file
334 system
335 system
335 * presumably is an Apple Python that uses Apple OpenSSL which has patches
336 * presumably is an Apple Python that uses Apple OpenSSL which has patches
336 for using system certificate store CAs in addition to the provided
337 for using system certificate store CAs in addition to the provided
337 cacerts file
338 cacerts file
338 """
339 """
339 if sys.platform != 'darwin' or util.mainfrozen() or not sys.executable:
340 if sys.platform != 'darwin' or util.mainfrozen() or not sys.executable:
340 return False
341 return False
341 exe = os.path.realpath(sys.executable).lower()
342 exe = os.path.realpath(sys.executable).lower()
342 return (exe.startswith('/usr/bin/python') or
343 return (exe.startswith('/usr/bin/python') or
343 exe.startswith('/system/library/frameworks/python.framework/'))
344 exe.startswith('/system/library/frameworks/python.framework/'))
344
345
345 def _defaultcacerts():
346 def _defaultcacerts():
346 """return path to default CA certificates or None."""
347 """return path to default CA certificates or None."""
347 if _plainapplepython():
348 if _plainapplepython():
348 dummycert = os.path.join(os.path.dirname(__file__), 'dummycert.pem')
349 dummycert = os.path.join(os.path.dirname(__file__), 'dummycert.pem')
349 if os.path.exists(dummycert):
350 if os.path.exists(dummycert):
350 return dummycert
351 return dummycert
351
352
352 return None
353 return None
353
354
354 def validatesocket(sock):
355 def validatesocket(sock):
355 """Validate a socket meets security requiremnets.
356 """Validate a socket meets security requiremnets.
356
357
357 The passed socket must have been created with ``wrapsocket()``.
358 The passed socket must have been created with ``wrapsocket()``.
358 """
359 """
359 host = sock._hgstate['hostname']
360 host = sock._hgstate['hostname']
360 ui = sock._hgstate['ui']
361 ui = sock._hgstate['ui']
361 settings = sock._hgstate['settings']
362 settings = sock._hgstate['settings']
362
363
363 try:
364 try:
364 peercert = sock.getpeercert(True)
365 peercert = sock.getpeercert(True)
365 peercert2 = sock.getpeercert()
366 peercert2 = sock.getpeercert()
366 except AttributeError:
367 except AttributeError:
367 raise error.Abort(_('%s ssl connection error') % host)
368 raise error.Abort(_('%s ssl connection error') % host)
368
369
369 if not peercert:
370 if not peercert:
370 raise error.Abort(_('%s certificate error: '
371 raise error.Abort(_('%s certificate error: '
371 'no certificate received') % host)
372 'no certificate received') % host)
372
373
373 if settings['disablecertverification']:
374 if settings['disablecertverification']:
374 # We don't print the certificate fingerprint because it shouldn't
375 # We don't print the certificate fingerprint because it shouldn't
375 # be necessary: if the user requested certificate verification be
376 # be necessary: if the user requested certificate verification be
376 # disabled, they presumably already saw a message about the inability
377 # disabled, they presumably already saw a message about the inability
377 # to verify the certificate and this message would have printed the
378 # to verify the certificate and this message would have printed the
378 # fingerprint. So printing the fingerprint here adds little to no
379 # fingerprint. So printing the fingerprint here adds little to no
379 # value.
380 # value.
380 ui.warn(_('warning: connection security to %s is disabled per current '
381 ui.warn(_('warning: connection security to %s is disabled per current '
381 'settings; communication is susceptible to eavesdropping '
382 'settings; communication is susceptible to eavesdropping '
382 'and tampering\n') % host)
383 'and tampering\n') % host)
383 return
384 return
384
385
385 # If a certificate fingerprint is pinned, use it and only it to
386 # If a certificate fingerprint is pinned, use it and only it to
386 # validate the remote cert.
387 # validate the remote cert.
387 peerfingerprints = {
388 peerfingerprints = {
388 'sha1': hashlib.sha1(peercert).hexdigest(),
389 'sha1': hashlib.sha1(peercert).hexdigest(),
389 'sha256': hashlib.sha256(peercert).hexdigest(),
390 'sha256': hashlib.sha256(peercert).hexdigest(),
390 'sha512': hashlib.sha512(peercert).hexdigest(),
391 'sha512': hashlib.sha512(peercert).hexdigest(),
391 }
392 }
392
393
393 def fmtfingerprint(s):
394 def fmtfingerprint(s):
394 return ':'.join([s[x:x + 2] for x in range(0, len(s), 2)])
395 return ':'.join([s[x:x + 2] for x in range(0, len(s), 2)])
395
396
396 nicefingerprint = 'sha256:%s' % fmtfingerprint(peerfingerprints['sha256'])
397 nicefingerprint = 'sha256:%s' % fmtfingerprint(peerfingerprints['sha256'])
397
398
398 if settings['certfingerprints']:
399 if settings['certfingerprints']:
399 for hash, fingerprint in settings['certfingerprints']:
400 for hash, fingerprint in settings['certfingerprints']:
400 if peerfingerprints[hash].lower() == fingerprint:
401 if peerfingerprints[hash].lower() == fingerprint:
401 ui.debug('%s certificate matched fingerprint %s:%s\n' %
402 ui.debug('%s certificate matched fingerprint %s:%s\n' %
402 (host, hash, fmtfingerprint(fingerprint)))
403 (host, hash, fmtfingerprint(fingerprint)))
403 return
404 return
404
405
405 # Pinned fingerprint didn't match. This is a fatal error.
406 # Pinned fingerprint didn't match. This is a fatal error.
406 if settings['legacyfingerprint']:
407 if settings['legacyfingerprint']:
407 section = 'hostfingerprint'
408 section = 'hostfingerprint'
408 nice = fmtfingerprint(peerfingerprints['sha1'])
409 nice = fmtfingerprint(peerfingerprints['sha1'])
409 else:
410 else:
410 section = 'hostsecurity'
411 section = 'hostsecurity'
411 nice = '%s:%s' % (hash, fmtfingerprint(peerfingerprints[hash]))
412 nice = '%s:%s' % (hash, fmtfingerprint(peerfingerprints[hash]))
412 raise error.Abort(_('certificate for %s has unexpected '
413 raise error.Abort(_('certificate for %s has unexpected '
413 'fingerprint %s') % (host, nice),
414 'fingerprint %s') % (host, nice),
414 hint=_('check %s configuration') % section)
415 hint=_('check %s configuration') % section)
415
416
417 # Security is enabled but no CAs are loaded. We can't establish trust
418 # for the cert so abort.
416 if not sock._hgstate['caloaded']:
419 if not sock._hgstate['caloaded']:
417 ui.warn(_('warning: certificate for %s not verified '
420 raise error.Abort(
418 '(set hostsecurity.%s:certfingerprints=%s or web.cacerts '
421 _('unable to verify security of %s (no loaded CA certificates); '
419 'config settings)\n') % (host, host, nicefingerprint))
422 'refusing to connect') % host,
420 return
423 hint=_('see https://mercurial-scm.org/wiki/SecureConnections for '
424 'how to configure Mercurial to avoid this error or set '
425 'hostsecurity.%s:fingerprints=%s to trust this server') %
426 (host, nicefingerprint))
421
427
422 msg = _verifycert(peercert2, host)
428 msg = _verifycert(peercert2, host)
423 if msg:
429 if msg:
424 raise error.Abort(_('%s certificate error: %s') % (host, msg),
430 raise error.Abort(_('%s certificate error: %s') % (host, msg),
425 hint=_('set hostsecurity.%s:certfingerprints=%s '
431 hint=_('set hostsecurity.%s:certfingerprints=%s '
426 'config setting or use --insecure to connect '
432 'config setting or use --insecure to connect '
427 'insecurely') %
433 'insecurely') %
428 (host, nicefingerprint))
434 (host, nicefingerprint))
@@ -1,369 +1,380
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 defaultcacerts
50 #if defaultcacerts
51 $ hg clone https://localhost:$HGPORT/ copy-pull
51 $ hg clone https://localhost:$HGPORT/ copy-pull
52 abort: error: *certificate verify failed* (glob)
52 abort: error: *certificate verify failed* (glob)
53 [255]
53 [255]
54 #endif
54 #endif
55
55
56 Specifying a per-host certificate file that doesn't exist will abort
56 Specifying a per-host certificate file that doesn't exist will abort
57
57
58 $ hg --config hostsecurity.localhost:verifycertsfile=/does/not/exist clone https://localhost:$HGPORT/
58 $ hg --config hostsecurity.localhost:verifycertsfile=/does/not/exist clone https://localhost:$HGPORT/
59 abort: path specified by hostsecurity.localhost:verifycertsfile does not exist: /does/not/exist
59 abort: path specified by hostsecurity.localhost:verifycertsfile does not exist: /does/not/exist
60 [255]
60 [255]
61
61
62 A malformed per-host certificate file will raise an error
62 A malformed per-host certificate file will raise an error
63
63
64 $ echo baddata > badca.pem
64 $ echo baddata > badca.pem
65 $ hg --config hostsecurity.localhost:verifycertsfile=badca.pem clone https://localhost:$HGPORT/
65 $ hg --config hostsecurity.localhost:verifycertsfile=badca.pem clone https://localhost:$HGPORT/
66 abort: error: * (glob)
66 abort: error: * (glob)
67 [255]
67 [255]
68
68
69 A per-host certificate mismatching the server will fail verification
69 A per-host certificate mismatching the server will fail verification
70
70
71 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/client-cert.pem" clone https://localhost:$HGPORT/
71 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/client-cert.pem" clone https://localhost:$HGPORT/
72 abort: error: *certificate verify failed* (glob)
72 abort: error: *certificate verify failed* (glob)
73 [255]
73 [255]
74
74
75 A per-host certificate matching the server's cert will be accepted
75 A per-host certificate matching the server's cert will be accepted
76
76
77 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/pub.pem" clone -U https://localhost:$HGPORT/ perhostgood1
77 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/pub.pem" clone -U https://localhost:$HGPORT/ perhostgood1
78 requesting all changes
78 requesting all changes
79 adding changesets
79 adding changesets
80 adding manifests
80 adding manifests
81 adding file changes
81 adding file changes
82 added 1 changesets with 4 changes to 4 files
82 added 1 changesets with 4 changes to 4 files
83
83
84 A per-host certificate with multiple certs and one matching will be accepted
84 A per-host certificate with multiple certs and one matching will be accepted
85
85
86 $ cat "$CERTSDIR/client-cert.pem" "$CERTSDIR/pub.pem" > perhost.pem
86 $ cat "$CERTSDIR/client-cert.pem" "$CERTSDIR/pub.pem" > perhost.pem
87 $ hg --config hostsecurity.localhost:verifycertsfile=perhost.pem clone -U https://localhost:$HGPORT/ perhostgood2
87 $ hg --config hostsecurity.localhost:verifycertsfile=perhost.pem clone -U https://localhost:$HGPORT/ perhostgood2
88 requesting all changes
88 requesting all changes
89 adding changesets
89 adding changesets
90 adding manifests
90 adding manifests
91 adding file changes
91 adding file changes
92 added 1 changesets with 4 changes to 4 files
92 added 1 changesets with 4 changes to 4 files
93
93
94 Defining both per-host certificate and a fingerprint will print a warning
94 Defining both per-host certificate and a fingerprint will print a warning
95
95
96 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/pub.pem" --config hostsecurity.localhost:fingerprints=sha1:914f1aff87249c09b6859b88b1906d30756491ca clone -U https://localhost:$HGPORT/ caandfingerwarning
96 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/pub.pem" --config hostsecurity.localhost:fingerprints=sha1:914f1aff87249c09b6859b88b1906d30756491ca clone -U https://localhost:$HGPORT/ caandfingerwarning
97 (hostsecurity.localhost:verifycertsfile ignored when host fingerprints defined; using host fingerprints for verification)
97 (hostsecurity.localhost:verifycertsfile ignored when host fingerprints defined; using host fingerprints for verification)
98 requesting all changes
98 requesting all changes
99 adding changesets
99 adding changesets
100 adding manifests
100 adding manifests
101 adding file changes
101 adding file changes
102 added 1 changesets with 4 changes to 4 files
102 added 1 changesets with 4 changes to 4 files
103
103
104 $ DISABLECACERTS="--config devel.disableloaddefaultcerts=true"
104 $ DISABLECACERTS="--config devel.disableloaddefaultcerts=true"
105
105
106 clone via pull
106 Inability to verify peer certificate will result in abort
107
107
108 $ hg clone https://localhost:$HGPORT/ copy-pull $DISABLECACERTS
108 $ hg clone https://localhost:$HGPORT/ copy-pull $DISABLECACERTS
109 warning: certificate for localhost not verified (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 or web.cacerts config settings)
109 abort: unable to verify security of localhost (no loaded CA certificates); refusing to connect
110 (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)
111 [255]
112
113 $ hg clone --insecure https://localhost:$HGPORT/ copy-pull
114 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
110 requesting all changes
115 requesting all changes
111 adding changesets
116 adding changesets
112 adding manifests
117 adding manifests
113 adding file changes
118 adding file changes
114 added 1 changesets with 4 changes to 4 files
119 added 1 changesets with 4 changes to 4 files
115 updating to branch default
120 updating to branch default
116 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
121 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
117 $ hg verify -R copy-pull
122 $ hg verify -R copy-pull
118 checking changesets
123 checking changesets
119 checking manifests
124 checking manifests
120 crosschecking files in changesets and manifests
125 crosschecking files in changesets and manifests
121 checking files
126 checking files
122 4 files, 1 changesets, 4 total revisions
127 4 files, 1 changesets, 4 total revisions
123 $ cd test
128 $ cd test
124 $ echo bar > bar
129 $ echo bar > bar
125 $ hg commit -A -d '1 0' -m 2
130 $ hg commit -A -d '1 0' -m 2
126 adding bar
131 adding bar
127 $ cd ..
132 $ cd ..
128
133
129 pull without cacert
134 pull without cacert
130
135
131 $ cd copy-pull
136 $ cd copy-pull
132 $ echo '[hooks]' >> .hg/hgrc
137 $ echo '[hooks]' >> .hg/hgrc
133 $ echo "changegroup = printenv.py changegroup" >> .hg/hgrc
138 $ echo "changegroup = printenv.py changegroup" >> .hg/hgrc
134 $ hg pull $DISABLECACERTS
139 $ hg pull $DISABLECACERTS
135 pulling from https://localhost:$HGPORT/
140 pulling from https://localhost:$HGPORT/
136 warning: certificate for localhost not verified (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 or web.cacerts config settings)
141 abort: unable to verify security of localhost (no loaded CA certificates); refusing to connect
142 (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)
143 [255]
144
145 $ hg pull --insecure
146 pulling from https://localhost:$HGPORT/
147 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
137 searching for changes
148 searching for changes
138 adding changesets
149 adding changesets
139 adding manifests
150 adding manifests
140 adding file changes
151 adding file changes
141 added 1 changesets with 1 changes to 1 files
152 added 1 changesets with 1 changes to 1 files
142 changegroup hook: HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_NODE_LAST=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=https://localhost:$HGPORT/ (glob)
153 changegroup hook: HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_NODE_LAST=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=https://localhost:$HGPORT/ (glob)
143 (run 'hg update' to get a working copy)
154 (run 'hg update' to get a working copy)
144 $ cd ..
155 $ cd ..
145
156
146 cacert configured in local repo
157 cacert configured in local repo
147
158
148 $ cp copy-pull/.hg/hgrc copy-pull/.hg/hgrc.bu
159 $ cp copy-pull/.hg/hgrc copy-pull/.hg/hgrc.bu
149 $ echo "[web]" >> copy-pull/.hg/hgrc
160 $ echo "[web]" >> copy-pull/.hg/hgrc
150 $ echo "cacerts=$CERTSDIR/pub.pem" >> copy-pull/.hg/hgrc
161 $ echo "cacerts=$CERTSDIR/pub.pem" >> copy-pull/.hg/hgrc
151 $ hg -R copy-pull pull --traceback
162 $ hg -R copy-pull pull --traceback
152 pulling from https://localhost:$HGPORT/
163 pulling from https://localhost:$HGPORT/
153 searching for changes
164 searching for changes
154 no changes found
165 no changes found
155 $ mv copy-pull/.hg/hgrc.bu copy-pull/.hg/hgrc
166 $ mv copy-pull/.hg/hgrc.bu copy-pull/.hg/hgrc
156
167
157 cacert configured globally, also testing expansion of environment
168 cacert configured globally, also testing expansion of environment
158 variables in the filename
169 variables in the filename
159
170
160 $ echo "[web]" >> $HGRCPATH
171 $ echo "[web]" >> $HGRCPATH
161 $ echo 'cacerts=$P/pub.pem' >> $HGRCPATH
172 $ echo 'cacerts=$P/pub.pem' >> $HGRCPATH
162 $ P="$CERTSDIR" hg -R copy-pull pull
173 $ P="$CERTSDIR" hg -R copy-pull pull
163 pulling from https://localhost:$HGPORT/
174 pulling from https://localhost:$HGPORT/
164 searching for changes
175 searching for changes
165 no changes found
176 no changes found
166 $ P="$CERTSDIR" hg -R copy-pull pull --insecure
177 $ P="$CERTSDIR" hg -R copy-pull pull --insecure
167 pulling from https://localhost:$HGPORT/
178 pulling from https://localhost:$HGPORT/
168 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
179 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
169 searching for changes
180 searching for changes
170 no changes found
181 no changes found
171
182
172 cacert mismatch
183 cacert mismatch
173
184
174 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub.pem" \
185 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub.pem" \
175 > https://127.0.0.1:$HGPORT/
186 > https://127.0.0.1:$HGPORT/
176 pulling from https://127.0.0.1:$HGPORT/
187 pulling from https://127.0.0.1:$HGPORT/
177 abort: 127.0.0.1 certificate error: certificate is for localhost
188 abort: 127.0.0.1 certificate error: certificate is for localhost
178 (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)
189 (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)
179 [255]
190 [255]
180 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub.pem" \
191 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub.pem" \
181 > https://127.0.0.1:$HGPORT/ --insecure
192 > https://127.0.0.1:$HGPORT/ --insecure
182 pulling from https://127.0.0.1:$HGPORT/
193 pulling from https://127.0.0.1:$HGPORT/
183 warning: connection security to 127.0.0.1 is disabled per current settings; communication is susceptible to eavesdropping and tampering
194 warning: connection security to 127.0.0.1 is disabled per current settings; communication is susceptible to eavesdropping and tampering
184 searching for changes
195 searching for changes
185 no changes found
196 no changes found
186 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-other.pem"
197 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-other.pem"
187 pulling from https://localhost:$HGPORT/
198 pulling from https://localhost:$HGPORT/
188 abort: error: *certificate verify failed* (glob)
199 abort: error: *certificate verify failed* (glob)
189 [255]
200 [255]
190 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-other.pem" \
201 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-other.pem" \
191 > --insecure
202 > --insecure
192 pulling from https://localhost:$HGPORT/
203 pulling from https://localhost:$HGPORT/
193 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
204 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
194 searching for changes
205 searching for changes
195 no changes found
206 no changes found
196
207
197 Test server cert which isn't valid yet
208 Test server cert which isn't valid yet
198
209
199 $ hg serve -R test -p $HGPORT1 -d --pid-file=hg1.pid --certificate=server-not-yet.pem
210 $ hg serve -R test -p $HGPORT1 -d --pid-file=hg1.pid --certificate=server-not-yet.pem
200 $ cat hg1.pid >> $DAEMON_PIDS
211 $ cat hg1.pid >> $DAEMON_PIDS
201 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-not-yet.pem" \
212 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-not-yet.pem" \
202 > https://localhost:$HGPORT1/
213 > https://localhost:$HGPORT1/
203 pulling from https://localhost:$HGPORT1/
214 pulling from https://localhost:$HGPORT1/
204 abort: error: *certificate verify failed* (glob)
215 abort: error: *certificate verify failed* (glob)
205 [255]
216 [255]
206
217
207 Test server cert which no longer is valid
218 Test server cert which no longer is valid
208
219
209 $ hg serve -R test -p $HGPORT2 -d --pid-file=hg2.pid --certificate=server-expired.pem
220 $ hg serve -R test -p $HGPORT2 -d --pid-file=hg2.pid --certificate=server-expired.pem
210 $ cat hg2.pid >> $DAEMON_PIDS
221 $ cat hg2.pid >> $DAEMON_PIDS
211 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-expired.pem" \
222 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-expired.pem" \
212 > https://localhost:$HGPORT2/
223 > https://localhost:$HGPORT2/
213 pulling from https://localhost:$HGPORT2/
224 pulling from https://localhost:$HGPORT2/
214 abort: error: *certificate verify failed* (glob)
225 abort: error: *certificate verify failed* (glob)
215 [255]
226 [255]
216
227
217 Fingerprints
228 Fingerprints
218
229
219 - works without cacerts (hostkeyfingerprints)
230 - works without cacerts (hostkeyfingerprints)
220 $ 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
231 $ 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
221 5fed3813f7f5
232 5fed3813f7f5
222
233
223 - works without cacerts (hostsecurity)
234 - works without cacerts (hostsecurity)
224 $ hg -R copy-pull id https://localhost:$HGPORT/ --config hostsecurity.localhost:fingerprints=sha1:914f1aff87249c09b6859b88b1906d30756491ca
235 $ hg -R copy-pull id https://localhost:$HGPORT/ --config hostsecurity.localhost:fingerprints=sha1:914f1aff87249c09b6859b88b1906d30756491ca
225 5fed3813f7f5
236 5fed3813f7f5
226
237
227 $ 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
238 $ 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
228 5fed3813f7f5
239 5fed3813f7f5
229
240
230 - multiple fingerprints specified and first matches
241 - multiple fingerprints specified and first matches
231 $ hg --config 'hostfingerprints.localhost=914f1aff87249c09b6859b88b1906d30756491ca, deadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/ --insecure
242 $ hg --config 'hostfingerprints.localhost=914f1aff87249c09b6859b88b1906d30756491ca, deadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/ --insecure
232 5fed3813f7f5
243 5fed3813f7f5
233
244
234 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:914f1aff87249c09b6859b88b1906d30756491ca, sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/
245 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:914f1aff87249c09b6859b88b1906d30756491ca, sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/
235 5fed3813f7f5
246 5fed3813f7f5
236
247
237 - multiple fingerprints specified and last matches
248 - multiple fingerprints specified and last matches
238 $ hg --config 'hostfingerprints.localhost=deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, 914f1aff87249c09b6859b88b1906d30756491ca' -R copy-pull id https://localhost:$HGPORT/ --insecure
249 $ hg --config 'hostfingerprints.localhost=deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, 914f1aff87249c09b6859b88b1906d30756491ca' -R copy-pull id https://localhost:$HGPORT/ --insecure
239 5fed3813f7f5
250 5fed3813f7f5
240
251
241 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, sha1:914f1aff87249c09b6859b88b1906d30756491ca' -R copy-pull id https://localhost:$HGPORT/
252 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, sha1:914f1aff87249c09b6859b88b1906d30756491ca' -R copy-pull id https://localhost:$HGPORT/
242 5fed3813f7f5
253 5fed3813f7f5
243
254
244 - multiple fingerprints specified and none match
255 - multiple fingerprints specified and none match
245
256
246 $ hg --config 'hostfingerprints.localhost=deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, aeadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/ --insecure
257 $ hg --config 'hostfingerprints.localhost=deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, aeadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/ --insecure
247 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
258 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
248 (check hostfingerprint configuration)
259 (check hostfingerprint configuration)
249 [255]
260 [255]
250
261
251 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, sha1:aeadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/
262 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, sha1:aeadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/
252 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
263 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
253 (check hostsecurity configuration)
264 (check hostsecurity configuration)
254 [255]
265 [255]
255
266
256 - fails when cert doesn't match hostname (port is ignored)
267 - fails when cert doesn't match hostname (port is ignored)
257 $ hg -R copy-pull id https://localhost:$HGPORT1/ --config hostfingerprints.localhost=914f1aff87249c09b6859b88b1906d30756491ca
268 $ hg -R copy-pull id https://localhost:$HGPORT1/ --config hostfingerprints.localhost=914f1aff87249c09b6859b88b1906d30756491ca
258 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
269 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
259 (check hostfingerprint configuration)
270 (check hostfingerprint configuration)
260 [255]
271 [255]
261
272
262
273
263 - ignores that certificate doesn't match hostname
274 - ignores that certificate doesn't match hostname
264 $ hg -R copy-pull id https://127.0.0.1:$HGPORT/ --config hostfingerprints.127.0.0.1=914f1aff87249c09b6859b88b1906d30756491ca
275 $ hg -R copy-pull id https://127.0.0.1:$HGPORT/ --config hostfingerprints.127.0.0.1=914f1aff87249c09b6859b88b1906d30756491ca
265 5fed3813f7f5
276 5fed3813f7f5
266
277
267 HGPORT1 is reused below for tinyproxy tests. Kill that server.
278 HGPORT1 is reused below for tinyproxy tests. Kill that server.
268 $ killdaemons.py hg1.pid
279 $ killdaemons.py hg1.pid
269
280
270 Prepare for connecting through proxy
281 Prepare for connecting through proxy
271
282
272 $ tinyproxy.py $HGPORT1 localhost >proxy.log </dev/null 2>&1 &
283 $ tinyproxy.py $HGPORT1 localhost >proxy.log </dev/null 2>&1 &
273 $ while [ ! -f proxy.pid ]; do sleep 0; done
284 $ while [ ! -f proxy.pid ]; do sleep 0; done
274 $ cat proxy.pid >> $DAEMON_PIDS
285 $ cat proxy.pid >> $DAEMON_PIDS
275
286
276 $ echo "[http_proxy]" >> copy-pull/.hg/hgrc
287 $ echo "[http_proxy]" >> copy-pull/.hg/hgrc
277 $ echo "always=True" >> copy-pull/.hg/hgrc
288 $ echo "always=True" >> copy-pull/.hg/hgrc
278 $ echo "[hostfingerprints]" >> copy-pull/.hg/hgrc
289 $ echo "[hostfingerprints]" >> copy-pull/.hg/hgrc
279 $ echo "localhost =" >> copy-pull/.hg/hgrc
290 $ echo "localhost =" >> copy-pull/.hg/hgrc
280
291
281 Test unvalidated https through proxy
292 Test unvalidated https through proxy
282
293
283 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --insecure --traceback
294 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --insecure --traceback
284 pulling from https://localhost:$HGPORT/
295 pulling from https://localhost:$HGPORT/
285 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
296 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
286 searching for changes
297 searching for changes
287 no changes found
298 no changes found
288
299
289 Test https with cacert and fingerprint through proxy
300 Test https with cacert and fingerprint through proxy
290
301
291 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull \
302 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull \
292 > --config web.cacerts="$CERTSDIR/pub.pem"
303 > --config web.cacerts="$CERTSDIR/pub.pem"
293 pulling from https://localhost:$HGPORT/
304 pulling from https://localhost:$HGPORT/
294 searching for changes
305 searching for changes
295 no changes found
306 no changes found
296 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull https://127.0.0.1:$HGPORT/ --config hostfingerprints.127.0.0.1=914f1aff87249c09b6859b88b1906d30756491ca
307 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull https://127.0.0.1:$HGPORT/ --config hostfingerprints.127.0.0.1=914f1aff87249c09b6859b88b1906d30756491ca
297 pulling from https://127.0.0.1:$HGPORT/
308 pulling from https://127.0.0.1:$HGPORT/
298 searching for changes
309 searching for changes
299 no changes found
310 no changes found
300
311
301 Test https with cert problems through proxy
312 Test https with cert problems through proxy
302
313
303 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull \
314 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull \
304 > --config web.cacerts="$CERTSDIR/pub-other.pem"
315 > --config web.cacerts="$CERTSDIR/pub-other.pem"
305 pulling from https://localhost:$HGPORT/
316 pulling from https://localhost:$HGPORT/
306 abort: error: *certificate verify failed* (glob)
317 abort: error: *certificate verify failed* (glob)
307 [255]
318 [255]
308 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull \
319 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull \
309 > --config web.cacerts="$CERTSDIR/pub-expired.pem" https://localhost:$HGPORT2/
320 > --config web.cacerts="$CERTSDIR/pub-expired.pem" https://localhost:$HGPORT2/
310 pulling from https://localhost:$HGPORT2/
321 pulling from https://localhost:$HGPORT2/
311 abort: error: *certificate verify failed* (glob)
322 abort: error: *certificate verify failed* (glob)
312 [255]
323 [255]
313
324
314
325
315 $ killdaemons.py hg0.pid
326 $ killdaemons.py hg0.pid
316
327
317 #if sslcontext
328 #if sslcontext
318
329
319 Start patched hgweb that requires client certificates:
330 Start patched hgweb that requires client certificates:
320
331
321 $ cat << EOT > reqclientcert.py
332 $ cat << EOT > reqclientcert.py
322 > import ssl
333 > import ssl
323 > from mercurial.hgweb import server
334 > from mercurial.hgweb import server
324 > class _httprequesthandlersslclientcert(server._httprequesthandlerssl):
335 > class _httprequesthandlersslclientcert(server._httprequesthandlerssl):
325 > @staticmethod
336 > @staticmethod
326 > def preparehttpserver(httpserver, ssl_cert):
337 > def preparehttpserver(httpserver, ssl_cert):
327 > sslcontext = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
338 > sslcontext = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
328 > sslcontext.verify_mode = ssl.CERT_REQUIRED
339 > sslcontext.verify_mode = ssl.CERT_REQUIRED
329 > sslcontext.load_cert_chain(ssl_cert)
340 > sslcontext.load_cert_chain(ssl_cert)
330 > # verify clients by server certificate
341 > # verify clients by server certificate
331 > sslcontext.load_verify_locations(ssl_cert)
342 > sslcontext.load_verify_locations(ssl_cert)
332 > httpserver.socket = sslcontext.wrap_socket(httpserver.socket,
343 > httpserver.socket = sslcontext.wrap_socket(httpserver.socket,
333 > server_side=True)
344 > server_side=True)
334 > server._httprequesthandlerssl = _httprequesthandlersslclientcert
345 > server._httprequesthandlerssl = _httprequesthandlersslclientcert
335 > EOT
346 > EOT
336 $ cd test
347 $ cd test
337 $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV \
348 $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV \
338 > --config extensions.reqclientcert=../reqclientcert.py
349 > --config extensions.reqclientcert=../reqclientcert.py
339 $ cat ../hg0.pid >> $DAEMON_PIDS
350 $ cat ../hg0.pid >> $DAEMON_PIDS
340 $ cd ..
351 $ cd ..
341
352
342 without client certificate:
353 without client certificate:
343
354
344 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/
355 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/
345 abort: error: *handshake failure* (glob)
356 abort: error: *handshake failure* (glob)
346 [255]
357 [255]
347
358
348 with client certificate:
359 with client certificate:
349
360
350 $ cat << EOT >> $HGRCPATH
361 $ cat << EOT >> $HGRCPATH
351 > [auth]
362 > [auth]
352 > l.prefix = localhost
363 > l.prefix = localhost
353 > l.cert = $CERTSDIR/client-cert.pem
364 > l.cert = $CERTSDIR/client-cert.pem
354 > l.key = $CERTSDIR/client-key.pem
365 > l.key = $CERTSDIR/client-key.pem
355 > EOT
366 > EOT
356
367
357 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
368 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
358 > --config auth.l.key="$CERTSDIR/client-key-decrypted.pem"
369 > --config auth.l.key="$CERTSDIR/client-key-decrypted.pem"
359 5fed3813f7f5
370 5fed3813f7f5
360
371
361 $ printf '1234\n' | env P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
372 $ printf '1234\n' | env P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
362 > --config ui.interactive=True --config ui.nontty=True
373 > --config ui.interactive=True --config ui.nontty=True
363 passphrase for */client-key.pem: 5fed3813f7f5 (glob)
374 passphrase for */client-key.pem: 5fed3813f7f5 (glob)
364
375
365 $ env P="$CERTSDIR" hg id https://localhost:$HGPORT/
376 $ env P="$CERTSDIR" hg id https://localhost:$HGPORT/
366 abort: error: * (glob)
377 abort: error: * (glob)
367 [255]
378 [255]
368
379
369 #endif
380 #endif
@@ -1,89 +1,90
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 defaultcacerts
46 #if defaultcacerts
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 (?i)abort: .*?certificate.verify.failed.* (re)
51 (?i)abort: .*?certificate.verify.failed.* (re)
52 [255]
52 [255]
53 #endif
53 #endif
54
54
55 $ DISABLECACERTS="--config devel.disableloaddefaultcerts=true"
55 $ DISABLECACERTS="--config devel.disableloaddefaultcerts=true"
56
56
57 Without certificates:
57 Without certificates:
58
58
59 $ try --debug
59 $ try --debug
60 this patch series consists of 1 patches.
60 this patch series consists of 1 patches.
61
61
62
62
63 (using smtps)
63 (using smtps)
64 sending mail: smtp host localhost, port * (glob)
64 sending mail: smtp host localhost, port * (glob)
65 (verifying remote certificate)
65 (verifying remote certificate)
66 warning: certificate for localhost not verified (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 or web.cacerts config settings)
66 abort: unable to verify security of localhost (no loaded CA certificates); refusing to connect
67 sending [PATCH] a ...
67 (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)
68 [255]
68
69
69 With global certificates:
70 With global certificates:
70
71
71 $ try --debug --config web.cacerts="$CERTSDIR/pub.pem"
72 $ try --debug --config web.cacerts="$CERTSDIR/pub.pem"
72 this patch series consists of 1 patches.
73 this patch series consists of 1 patches.
73
74
74
75
75 (using smtps)
76 (using smtps)
76 sending mail: smtp host localhost, port * (glob)
77 sending mail: smtp host localhost, port * (glob)
77 (verifying remote certificate)
78 (verifying remote certificate)
78 sending [PATCH] a ...
79 sending [PATCH] a ...
79
80
80 With invalid certificates:
81 With invalid certificates:
81
82
82 $ try --config web.cacerts="$CERTSDIR/pub-other.pem"
83 $ try --config web.cacerts="$CERTSDIR/pub-other.pem"
83 this patch series consists of 1 patches.
84 this patch series consists of 1 patches.
84
85
85
86
86 (?i)abort: .*?certificate.verify.failed.* (re)
87 (?i)abort: .*?certificate.verify.failed.* (re)
87 [255]
88 [255]
88
89
89 $ cd ..
90 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now