##// END OF EJS Templates
sslutil: update comment about create_default_context()...
Gregory Szorc -
r29557:53de8255 default
parent child Browse files
Show More
@@ -1,656 +1,662 b''
1 # sslutil.py - SSL handling for mercurial
1 # sslutil.py - SSL handling for mercurial
2 #
2 #
3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 from __future__ import absolute_import
10 from __future__ import absolute_import
11
11
12 import hashlib
12 import hashlib
13 import os
13 import os
14 import re
14 import re
15 import ssl
15 import ssl
16 import sys
16 import sys
17
17
18 from .i18n import _
18 from .i18n import _
19 from . import (
19 from . import (
20 error,
20 error,
21 util,
21 util,
22 )
22 )
23
23
24 # Python 2.7.9+ overhauled the built-in SSL/TLS features of Python. It added
24 # Python 2.7.9+ overhauled the built-in SSL/TLS features of Python. It added
25 # support for TLS 1.1, TLS 1.2, SNI, system CA stores, etc. These features are
25 # support for TLS 1.1, TLS 1.2, SNI, system CA stores, etc. These features are
26 # all exposed via the "ssl" module.
26 # all exposed via the "ssl" module.
27 #
27 #
28 # Depending on the version of Python being used, SSL/TLS support is either
28 # Depending on the version of Python being used, SSL/TLS support is either
29 # modern/secure or legacy/insecure. Many operations in this module have
29 # modern/secure or legacy/insecure. Many operations in this module have
30 # separate code paths depending on support in Python.
30 # separate code paths depending on support in Python.
31
31
32 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 # PROTOCOL_* constant to use for SSLContext.__init__.
129 # PROTOCOL_* constant to use for SSLContext.__init__.
130 'protocol': None,
130 'protocol': None,
131 # ssl.CERT_* constant used by SSLContext.verify_mode.
131 # ssl.CERT_* constant used by SSLContext.verify_mode.
132 'verifymode': None,
132 'verifymode': None,
133 # Defines extra ssl.OP* bitwise options to set.
133 # Defines extra ssl.OP* bitwise options to set.
134 'ctxoptions': None,
134 'ctxoptions': None,
135 }
135 }
136
136
137 # Despite its name, PROTOCOL_SSLv23 selects the highest protocol
137 # Despite its name, PROTOCOL_SSLv23 selects the highest protocol
138 # that both ends support, including TLS protocols. On legacy stacks,
138 # that both ends support, including TLS protocols. On legacy stacks,
139 # the highest it likely goes in TLS 1.0. On modern stacks, it can
139 # the highest it likely goes in TLS 1.0. On modern stacks, it can
140 # support TLS 1.2.
140 # support TLS 1.2.
141 #
141 #
142 # The PROTOCOL_TLSv* constants select a specific TLS version
142 # The PROTOCOL_TLSv* constants select a specific TLS version
143 # only (as opposed to multiple versions). So the method for
143 # only (as opposed to multiple versions). So the method for
144 # supporting multiple TLS versions is to use PROTOCOL_SSLv23 and
144 # supporting multiple TLS versions is to use PROTOCOL_SSLv23 and
145 # disable protocols via SSLContext.options and OP_NO_* constants.
145 # disable protocols via SSLContext.options and OP_NO_* constants.
146 # However, SSLContext.options doesn't work unless we have the
146 # However, SSLContext.options doesn't work unless we have the
147 # full/real SSLContext available to us.
147 # full/real SSLContext available to us.
148 if modernssl:
148 if modernssl:
149 s['protocol'] = ssl.PROTOCOL_SSLv23
149 s['protocol'] = ssl.PROTOCOL_SSLv23
150 else:
150 else:
151 s['protocol'] = ssl.PROTOCOL_TLSv1
151 s['protocol'] = ssl.PROTOCOL_TLSv1
152
152
153 # SSLv2 and SSLv3 are broken. We ban them outright.
153 # SSLv2 and SSLv3 are broken. We ban them outright.
154 # WARNING: ctxoptions doesn't have an effect unless the modern ssl module
154 # WARNING: ctxoptions doesn't have an effect unless the modern ssl module
155 # is available. Be careful when adding flags!
155 # is available. Be careful when adding flags!
156 s['ctxoptions'] = OP_NO_SSLv2 | OP_NO_SSLv3
156 s['ctxoptions'] = OP_NO_SSLv2 | OP_NO_SSLv3
157
157
158 # Look for fingerprints in [hostsecurity] section. Value is a list
158 # Look for fingerprints in [hostsecurity] section. Value is a list
159 # of <alg>:<fingerprint> strings.
159 # of <alg>:<fingerprint> strings.
160 fingerprints = ui.configlist('hostsecurity', '%s:fingerprints' % hostname,
160 fingerprints = ui.configlist('hostsecurity', '%s:fingerprints' % hostname,
161 [])
161 [])
162 for fingerprint in fingerprints:
162 for fingerprint in fingerprints:
163 if not (fingerprint.startswith(('sha1:', 'sha256:', 'sha512:'))):
163 if not (fingerprint.startswith(('sha1:', 'sha256:', 'sha512:'))):
164 raise error.Abort(_('invalid fingerprint for %s: %s') % (
164 raise error.Abort(_('invalid fingerprint for %s: %s') % (
165 hostname, fingerprint),
165 hostname, fingerprint),
166 hint=_('must begin with "sha1:", "sha256:", '
166 hint=_('must begin with "sha1:", "sha256:", '
167 'or "sha512:"'))
167 'or "sha512:"'))
168
168
169 alg, fingerprint = fingerprint.split(':', 1)
169 alg, fingerprint = fingerprint.split(':', 1)
170 fingerprint = fingerprint.replace(':', '').lower()
170 fingerprint = fingerprint.replace(':', '').lower()
171 s['certfingerprints'].append((alg, fingerprint))
171 s['certfingerprints'].append((alg, fingerprint))
172
172
173 # Fingerprints from [hostfingerprints] are always SHA-1.
173 # Fingerprints from [hostfingerprints] are always SHA-1.
174 for fingerprint in ui.configlist('hostfingerprints', hostname, []):
174 for fingerprint in ui.configlist('hostfingerprints', hostname, []):
175 fingerprint = fingerprint.replace(':', '').lower()
175 fingerprint = fingerprint.replace(':', '').lower()
176 s['certfingerprints'].append(('sha1', fingerprint))
176 s['certfingerprints'].append(('sha1', fingerprint))
177 s['legacyfingerprint'] = True
177 s['legacyfingerprint'] = True
178
178
179 # If a host cert fingerprint is defined, it is the only thing that
179 # If a host cert fingerprint is defined, it is the only thing that
180 # matters. No need to validate CA certs.
180 # matters. No need to validate CA certs.
181 if s['certfingerprints']:
181 if s['certfingerprints']:
182 s['verifymode'] = ssl.CERT_NONE
182 s['verifymode'] = ssl.CERT_NONE
183 s['allowloaddefaultcerts'] = False
183 s['allowloaddefaultcerts'] = False
184
184
185 # If --insecure is used, don't take CAs into consideration.
185 # If --insecure is used, don't take CAs into consideration.
186 elif ui.insecureconnections:
186 elif ui.insecureconnections:
187 s['disablecertverification'] = True
187 s['disablecertverification'] = True
188 s['verifymode'] = ssl.CERT_NONE
188 s['verifymode'] = ssl.CERT_NONE
189 s['allowloaddefaultcerts'] = False
189 s['allowloaddefaultcerts'] = False
190
190
191 if ui.configbool('devel', 'disableloaddefaultcerts'):
191 if ui.configbool('devel', 'disableloaddefaultcerts'):
192 s['allowloaddefaultcerts'] = False
192 s['allowloaddefaultcerts'] = False
193
193
194 # If both fingerprints and a per-host ca file are specified, issue a warning
194 # If both fingerprints and a per-host ca file are specified, issue a warning
195 # because users should not be surprised about what security is or isn't
195 # because users should not be surprised about what security is or isn't
196 # being performed.
196 # being performed.
197 cafile = ui.config('hostsecurity', '%s:verifycertsfile' % hostname)
197 cafile = ui.config('hostsecurity', '%s:verifycertsfile' % hostname)
198 if s['certfingerprints'] and cafile:
198 if s['certfingerprints'] and cafile:
199 ui.warn(_('(hostsecurity.%s:verifycertsfile ignored when host '
199 ui.warn(_('(hostsecurity.%s:verifycertsfile ignored when host '
200 'fingerprints defined; using host fingerprints for '
200 'fingerprints defined; using host fingerprints for '
201 'verification)\n') % hostname)
201 'verification)\n') % hostname)
202
202
203 # Try to hook up CA certificate validation unless something above
203 # Try to hook up CA certificate validation unless something above
204 # makes it not necessary.
204 # makes it not necessary.
205 if s['verifymode'] is None:
205 if s['verifymode'] is None:
206 # Look at per-host ca file first.
206 # Look at per-host ca file first.
207 if cafile:
207 if cafile:
208 cafile = util.expandpath(cafile)
208 cafile = util.expandpath(cafile)
209 if not os.path.exists(cafile):
209 if not os.path.exists(cafile):
210 raise error.Abort(_('path specified by %s does not exist: %s') %
210 raise error.Abort(_('path specified by %s does not exist: %s') %
211 ('hostsecurity.%s:verifycertsfile' % hostname,
211 ('hostsecurity.%s:verifycertsfile' % hostname,
212 cafile))
212 cafile))
213 s['cafile'] = cafile
213 s['cafile'] = cafile
214 else:
214 else:
215 # Find global certificates file in config.
215 # Find global certificates file in config.
216 cafile = ui.config('web', 'cacerts')
216 cafile = ui.config('web', 'cacerts')
217
217
218 if cafile:
218 if cafile:
219 cafile = util.expandpath(cafile)
219 cafile = util.expandpath(cafile)
220 if not os.path.exists(cafile):
220 if not os.path.exists(cafile):
221 raise error.Abort(_('could not find web.cacerts: %s') %
221 raise error.Abort(_('could not find web.cacerts: %s') %
222 cafile)
222 cafile)
223 elif s['allowloaddefaultcerts']:
223 elif s['allowloaddefaultcerts']:
224 # CAs not defined in config. Try to find system bundles.
224 # CAs not defined in config. Try to find system bundles.
225 cafile = _defaultcacerts(ui)
225 cafile = _defaultcacerts(ui)
226 if cafile:
226 if cafile:
227 ui.debug('using %s for CA file\n' % cafile)
227 ui.debug('using %s for CA file\n' % cafile)
228
228
229 s['cafile'] = cafile
229 s['cafile'] = cafile
230
230
231 # Require certificate validation if CA certs are being loaded and
231 # Require certificate validation if CA certs are being loaded and
232 # verification hasn't been disabled above.
232 # verification hasn't been disabled above.
233 if cafile or (_canloaddefaultcerts and s['allowloaddefaultcerts']):
233 if cafile or (_canloaddefaultcerts and s['allowloaddefaultcerts']):
234 s['verifymode'] = ssl.CERT_REQUIRED
234 s['verifymode'] = ssl.CERT_REQUIRED
235 else:
235 else:
236 # At this point we don't have a fingerprint, aren't being
236 # At this point we don't have a fingerprint, aren't being
237 # explicitly insecure, and can't load CA certs. Connecting
237 # explicitly insecure, and can't load CA certs. Connecting
238 # is insecure. We allow the connection and abort during
238 # is insecure. We allow the connection and abort during
239 # validation (once we have the fingerprint to print to the
239 # validation (once we have the fingerprint to print to the
240 # user).
240 # user).
241 s['verifymode'] = ssl.CERT_NONE
241 s['verifymode'] = ssl.CERT_NONE
242
242
243 assert s['protocol'] is not None
243 assert s['protocol'] is not None
244 assert s['ctxoptions'] is not None
244 assert s['ctxoptions'] is not None
245 assert s['verifymode'] is not None
245 assert s['verifymode'] is not None
246
246
247 return s
247 return s
248
248
249 def wrapsocket(sock, keyfile, certfile, ui, serverhostname=None):
249 def wrapsocket(sock, keyfile, certfile, ui, serverhostname=None):
250 """Add SSL/TLS to a socket.
250 """Add SSL/TLS to a socket.
251
251
252 This is a glorified wrapper for ``ssl.wrap_socket()``. It makes sane
252 This is a glorified wrapper for ``ssl.wrap_socket()``. It makes sane
253 choices based on what security options are available.
253 choices based on what security options are available.
254
254
255 In addition to the arguments supported by ``ssl.wrap_socket``, we allow
255 In addition to the arguments supported by ``ssl.wrap_socket``, we allow
256 the following additional arguments:
256 the following additional arguments:
257
257
258 * serverhostname - The expected hostname of the remote server. If the
258 * serverhostname - The expected hostname of the remote server. If the
259 server (and client) support SNI, this tells the server which certificate
259 server (and client) support SNI, this tells the server which certificate
260 to use.
260 to use.
261 """
261 """
262 if not serverhostname:
262 if not serverhostname:
263 raise error.Abort(_('serverhostname argument is required'))
263 raise error.Abort(_('serverhostname argument is required'))
264
264
265 settings = _hostsettings(ui, serverhostname)
265 settings = _hostsettings(ui, serverhostname)
266
266
267 # TODO use ssl.create_default_context() on modernssl.
267 # We can't use ssl.create_default_context() because it calls
268 # load_default_certs() unless CA arguments are passed to it. We want to
269 # have explicit control over CA loading because implicitly loading
270 # CAs may undermine the user's intent. For example, a user may define a CA
271 # bundle with a specific CA cert removed. If the system/default CA bundle
272 # is loaded and contains that removed CA, you've just undone the user's
273 # choice.
268 sslcontext = SSLContext(settings['protocol'])
274 sslcontext = SSLContext(settings['protocol'])
269
275
270 # This is a no-op unless using modern ssl.
276 # This is a no-op unless using modern ssl.
271 sslcontext.options |= settings['ctxoptions']
277 sslcontext.options |= settings['ctxoptions']
272
278
273 # This still works on our fake SSLContext.
279 # This still works on our fake SSLContext.
274 sslcontext.verify_mode = settings['verifymode']
280 sslcontext.verify_mode = settings['verifymode']
275
281
276 if certfile is not None:
282 if certfile is not None:
277 def password():
283 def password():
278 f = keyfile or certfile
284 f = keyfile or certfile
279 return ui.getpass(_('passphrase for %s: ') % f, '')
285 return ui.getpass(_('passphrase for %s: ') % f, '')
280 sslcontext.load_cert_chain(certfile, keyfile, password)
286 sslcontext.load_cert_chain(certfile, keyfile, password)
281
287
282 if settings['cafile'] is not None:
288 if settings['cafile'] is not None:
283 try:
289 try:
284 sslcontext.load_verify_locations(cafile=settings['cafile'])
290 sslcontext.load_verify_locations(cafile=settings['cafile'])
285 except ssl.SSLError as e:
291 except ssl.SSLError as e:
286 raise error.Abort(_('error loading CA file %s: %s') % (
292 raise error.Abort(_('error loading CA file %s: %s') % (
287 settings['cafile'], e.args[1]),
293 settings['cafile'], e.args[1]),
288 hint=_('file is empty or malformed?'))
294 hint=_('file is empty or malformed?'))
289 caloaded = True
295 caloaded = True
290 elif settings['allowloaddefaultcerts']:
296 elif settings['allowloaddefaultcerts']:
291 # This is a no-op on old Python.
297 # This is a no-op on old Python.
292 sslcontext.load_default_certs()
298 sslcontext.load_default_certs()
293 caloaded = True
299 caloaded = True
294 else:
300 else:
295 caloaded = False
301 caloaded = False
296
302
297 try:
303 try:
298 sslsocket = sslcontext.wrap_socket(sock, server_hostname=serverhostname)
304 sslsocket = sslcontext.wrap_socket(sock, server_hostname=serverhostname)
299 except ssl.SSLError:
305 except ssl.SSLError:
300 # If we're doing certificate verification and no CA certs are loaded,
306 # If we're doing certificate verification and no CA certs are loaded,
301 # that is almost certainly the reason why verification failed. Provide
307 # that is almost certainly the reason why verification failed. Provide
302 # a hint to the user.
308 # a hint to the user.
303 # Only modern ssl module exposes SSLContext.get_ca_certs() so we can
309 # Only modern ssl module exposes SSLContext.get_ca_certs() so we can
304 # only show this warning if modern ssl is available.
310 # only show this warning if modern ssl is available.
305 if (caloaded and settings['verifymode'] == ssl.CERT_REQUIRED and
311 if (caloaded and settings['verifymode'] == ssl.CERT_REQUIRED and
306 modernssl and not sslcontext.get_ca_certs()):
312 modernssl and not sslcontext.get_ca_certs()):
307 ui.warn(_('(an attempt was made to load CA certificates but none '
313 ui.warn(_('(an attempt was made to load CA certificates but none '
308 'were loaded; see '
314 'were loaded; see '
309 'https://mercurial-scm.org/wiki/SecureConnections for '
315 'https://mercurial-scm.org/wiki/SecureConnections for '
310 'how to configure Mercurial to avoid this error)\n'))
316 'how to configure Mercurial to avoid this error)\n'))
311 raise
317 raise
312
318
313 # check if wrap_socket failed silently because socket had been
319 # check if wrap_socket failed silently because socket had been
314 # closed
320 # closed
315 # - see http://bugs.python.org/issue13721
321 # - see http://bugs.python.org/issue13721
316 if not sslsocket.cipher():
322 if not sslsocket.cipher():
317 raise error.Abort(_('ssl connection failed'))
323 raise error.Abort(_('ssl connection failed'))
318
324
319 sslsocket._hgstate = {
325 sslsocket._hgstate = {
320 'caloaded': caloaded,
326 'caloaded': caloaded,
321 'hostname': serverhostname,
327 'hostname': serverhostname,
322 'settings': settings,
328 'settings': settings,
323 'ui': ui,
329 'ui': ui,
324 }
330 }
325
331
326 return sslsocket
332 return sslsocket
327
333
328 def wrapserversocket(sock, ui, certfile=None, keyfile=None, cafile=None,
334 def wrapserversocket(sock, ui, certfile=None, keyfile=None, cafile=None,
329 requireclientcert=False):
335 requireclientcert=False):
330 """Wrap a socket for use by servers.
336 """Wrap a socket for use by servers.
331
337
332 ``certfile`` and ``keyfile`` specify the files containing the certificate's
338 ``certfile`` and ``keyfile`` specify the files containing the certificate's
333 public and private keys, respectively. Both keys can be defined in the same
339 public and private keys, respectively. Both keys can be defined in the same
334 file via ``certfile`` (the private key must come first in the file).
340 file via ``certfile`` (the private key must come first in the file).
335
341
336 ``cafile`` defines the path to certificate authorities.
342 ``cafile`` defines the path to certificate authorities.
337
343
338 ``requireclientcert`` specifies whether to require client certificates.
344 ``requireclientcert`` specifies whether to require client certificates.
339
345
340 Typically ``cafile`` is only defined if ``requireclientcert`` is true.
346 Typically ``cafile`` is only defined if ``requireclientcert`` is true.
341 """
347 """
342 if modernssl:
348 if modernssl:
343 # We /could/ use create_default_context() here since it doesn't load
349 # We /could/ use create_default_context() here since it doesn't load
344 # CAs when configured for client auth.
350 # CAs when configured for client auth.
345 sslcontext = SSLContext(ssl.PROTOCOL_SSLv23)
351 sslcontext = SSLContext(ssl.PROTOCOL_SSLv23)
346 # SSLv2 and SSLv3 are broken. Ban them outright.
352 # SSLv2 and SSLv3 are broken. Ban them outright.
347 sslcontext.options |= OP_NO_SSLv2 | OP_NO_SSLv3
353 sslcontext.options |= OP_NO_SSLv2 | OP_NO_SSLv3
348 # Prevent CRIME
354 # Prevent CRIME
349 sslcontext.options |= getattr(ssl, 'OP_NO_COMPRESSION', 0)
355 sslcontext.options |= getattr(ssl, 'OP_NO_COMPRESSION', 0)
350 # Improve forward secrecy.
356 # Improve forward secrecy.
351 sslcontext.options |= getattr(ssl, 'OP_SINGLE_DH_USE', 0)
357 sslcontext.options |= getattr(ssl, 'OP_SINGLE_DH_USE', 0)
352 sslcontext.options |= getattr(ssl, 'OP_SINGLE_ECDH_USE', 0)
358 sslcontext.options |= getattr(ssl, 'OP_SINGLE_ECDH_USE', 0)
353
359
354 # Use the list of more secure ciphers if found in the ssl module.
360 # Use the list of more secure ciphers if found in the ssl module.
355 if util.safehasattr(ssl, '_RESTRICTED_SERVER_CIPHERS'):
361 if util.safehasattr(ssl, '_RESTRICTED_SERVER_CIPHERS'):
356 sslcontext.options |= getattr(ssl, 'OP_CIPHER_SERVER_PREFERENCE', 0)
362 sslcontext.options |= getattr(ssl, 'OP_CIPHER_SERVER_PREFERENCE', 0)
357 sslcontext.set_ciphers(ssl._RESTRICTED_SERVER_CIPHERS)
363 sslcontext.set_ciphers(ssl._RESTRICTED_SERVER_CIPHERS)
358 else:
364 else:
359 sslcontext = SSLContext(ssl.PROTOCOL_TLSv1)
365 sslcontext = SSLContext(ssl.PROTOCOL_TLSv1)
360
366
361 if requireclientcert:
367 if requireclientcert:
362 sslcontext.verify_mode = ssl.CERT_REQUIRED
368 sslcontext.verify_mode = ssl.CERT_REQUIRED
363 else:
369 else:
364 sslcontext.verify_mode = ssl.CERT_NONE
370 sslcontext.verify_mode = ssl.CERT_NONE
365
371
366 if certfile or keyfile:
372 if certfile or keyfile:
367 sslcontext.load_cert_chain(certfile=certfile, keyfile=keyfile)
373 sslcontext.load_cert_chain(certfile=certfile, keyfile=keyfile)
368
374
369 if cafile:
375 if cafile:
370 sslcontext.load_verify_locations(cafile=cafile)
376 sslcontext.load_verify_locations(cafile=cafile)
371
377
372 return sslcontext.wrap_socket(sock, server_side=True)
378 return sslcontext.wrap_socket(sock, server_side=True)
373
379
374 class wildcarderror(Exception):
380 class wildcarderror(Exception):
375 """Represents an error parsing wildcards in DNS name."""
381 """Represents an error parsing wildcards in DNS name."""
376
382
377 def _dnsnamematch(dn, hostname, maxwildcards=1):
383 def _dnsnamematch(dn, hostname, maxwildcards=1):
378 """Match DNS names according RFC 6125 section 6.4.3.
384 """Match DNS names according RFC 6125 section 6.4.3.
379
385
380 This code is effectively copied from CPython's ssl._dnsname_match.
386 This code is effectively copied from CPython's ssl._dnsname_match.
381
387
382 Returns a bool indicating whether the expected hostname matches
388 Returns a bool indicating whether the expected hostname matches
383 the value in ``dn``.
389 the value in ``dn``.
384 """
390 """
385 pats = []
391 pats = []
386 if not dn:
392 if not dn:
387 return False
393 return False
388
394
389 pieces = dn.split(r'.')
395 pieces = dn.split(r'.')
390 leftmost = pieces[0]
396 leftmost = pieces[0]
391 remainder = pieces[1:]
397 remainder = pieces[1:]
392 wildcards = leftmost.count('*')
398 wildcards = leftmost.count('*')
393 if wildcards > maxwildcards:
399 if wildcards > maxwildcards:
394 raise wildcarderror(
400 raise wildcarderror(
395 _('too many wildcards in certificate DNS name: %s') % dn)
401 _('too many wildcards in certificate DNS name: %s') % dn)
396
402
397 # speed up common case w/o wildcards
403 # speed up common case w/o wildcards
398 if not wildcards:
404 if not wildcards:
399 return dn.lower() == hostname.lower()
405 return dn.lower() == hostname.lower()
400
406
401 # RFC 6125, section 6.4.3, subitem 1.
407 # RFC 6125, section 6.4.3, subitem 1.
402 # The client SHOULD NOT attempt to match a presented identifier in which
408 # The client SHOULD NOT attempt to match a presented identifier in which
403 # the wildcard character comprises a label other than the left-most label.
409 # the wildcard character comprises a label other than the left-most label.
404 if leftmost == '*':
410 if leftmost == '*':
405 # When '*' is a fragment by itself, it matches a non-empty dotless
411 # When '*' is a fragment by itself, it matches a non-empty dotless
406 # fragment.
412 # fragment.
407 pats.append('[^.]+')
413 pats.append('[^.]+')
408 elif leftmost.startswith('xn--') or hostname.startswith('xn--'):
414 elif leftmost.startswith('xn--') or hostname.startswith('xn--'):
409 # RFC 6125, section 6.4.3, subitem 3.
415 # RFC 6125, section 6.4.3, subitem 3.
410 # The client SHOULD NOT attempt to match a presented identifier
416 # The client SHOULD NOT attempt to match a presented identifier
411 # where the wildcard character is embedded within an A-label or
417 # where the wildcard character is embedded within an A-label or
412 # U-label of an internationalized domain name.
418 # U-label of an internationalized domain name.
413 pats.append(re.escape(leftmost))
419 pats.append(re.escape(leftmost))
414 else:
420 else:
415 # Otherwise, '*' matches any dotless string, e.g. www*
421 # Otherwise, '*' matches any dotless string, e.g. www*
416 pats.append(re.escape(leftmost).replace(r'\*', '[^.]*'))
422 pats.append(re.escape(leftmost).replace(r'\*', '[^.]*'))
417
423
418 # add the remaining fragments, ignore any wildcards
424 # add the remaining fragments, ignore any wildcards
419 for frag in remainder:
425 for frag in remainder:
420 pats.append(re.escape(frag))
426 pats.append(re.escape(frag))
421
427
422 pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)
428 pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)
423 return pat.match(hostname) is not None
429 return pat.match(hostname) is not None
424
430
425 def _verifycert(cert, hostname):
431 def _verifycert(cert, hostname):
426 '''Verify that cert (in socket.getpeercert() format) matches hostname.
432 '''Verify that cert (in socket.getpeercert() format) matches hostname.
427 CRLs is not handled.
433 CRLs is not handled.
428
434
429 Returns error message if any problems are found and None on success.
435 Returns error message if any problems are found and None on success.
430 '''
436 '''
431 if not cert:
437 if not cert:
432 return _('no certificate received')
438 return _('no certificate received')
433
439
434 dnsnames = []
440 dnsnames = []
435 san = cert.get('subjectAltName', [])
441 san = cert.get('subjectAltName', [])
436 for key, value in san:
442 for key, value in san:
437 if key == 'DNS':
443 if key == 'DNS':
438 try:
444 try:
439 if _dnsnamematch(value, hostname):
445 if _dnsnamematch(value, hostname):
440 return
446 return
441 except wildcarderror as e:
447 except wildcarderror as e:
442 return e.args[0]
448 return e.args[0]
443
449
444 dnsnames.append(value)
450 dnsnames.append(value)
445
451
446 if not dnsnames:
452 if not dnsnames:
447 # The subject is only checked when there is no DNS in subjectAltName.
453 # The subject is only checked when there is no DNS in subjectAltName.
448 for sub in cert.get('subject', []):
454 for sub in cert.get('subject', []):
449 for key, value in sub:
455 for key, value in sub:
450 # According to RFC 2818 the most specific Common Name must
456 # According to RFC 2818 the most specific Common Name must
451 # be used.
457 # be used.
452 if key == 'commonName':
458 if key == 'commonName':
453 # 'subject' entries are unicide.
459 # 'subject' entries are unicide.
454 try:
460 try:
455 value = value.encode('ascii')
461 value = value.encode('ascii')
456 except UnicodeEncodeError:
462 except UnicodeEncodeError:
457 return _('IDN in certificate not supported')
463 return _('IDN in certificate not supported')
458
464
459 try:
465 try:
460 if _dnsnamematch(value, hostname):
466 if _dnsnamematch(value, hostname):
461 return
467 return
462 except wildcarderror as e:
468 except wildcarderror as e:
463 return e.args[0]
469 return e.args[0]
464
470
465 dnsnames.append(value)
471 dnsnames.append(value)
466
472
467 if len(dnsnames) > 1:
473 if len(dnsnames) > 1:
468 return _('certificate is for %s') % ', '.join(dnsnames)
474 return _('certificate is for %s') % ', '.join(dnsnames)
469 elif len(dnsnames) == 1:
475 elif len(dnsnames) == 1:
470 return _('certificate is for %s') % dnsnames[0]
476 return _('certificate is for %s') % dnsnames[0]
471 else:
477 else:
472 return _('no commonName or subjectAltName found in certificate')
478 return _('no commonName or subjectAltName found in certificate')
473
479
474 def _plainapplepython():
480 def _plainapplepython():
475 """return true if this seems to be a pure Apple Python that
481 """return true if this seems to be a pure Apple Python that
476 * is unfrozen and presumably has the whole mercurial module in the file
482 * is unfrozen and presumably has the whole mercurial module in the file
477 system
483 system
478 * presumably is an Apple Python that uses Apple OpenSSL which has patches
484 * presumably is an Apple Python that uses Apple OpenSSL which has patches
479 for using system certificate store CAs in addition to the provided
485 for using system certificate store CAs in addition to the provided
480 cacerts file
486 cacerts file
481 """
487 """
482 if sys.platform != 'darwin' or util.mainfrozen() or not sys.executable:
488 if sys.platform != 'darwin' or util.mainfrozen() or not sys.executable:
483 return False
489 return False
484 exe = os.path.realpath(sys.executable).lower()
490 exe = os.path.realpath(sys.executable).lower()
485 return (exe.startswith('/usr/bin/python') or
491 return (exe.startswith('/usr/bin/python') or
486 exe.startswith('/system/library/frameworks/python.framework/'))
492 exe.startswith('/system/library/frameworks/python.framework/'))
487
493
488 _systemcacertpaths = [
494 _systemcacertpaths = [
489 # RHEL, CentOS, and Fedora
495 # RHEL, CentOS, and Fedora
490 '/etc/pki/tls/certs/ca-bundle.trust.crt',
496 '/etc/pki/tls/certs/ca-bundle.trust.crt',
491 # Debian, Ubuntu, Gentoo
497 # Debian, Ubuntu, Gentoo
492 '/etc/ssl/certs/ca-certificates.crt',
498 '/etc/ssl/certs/ca-certificates.crt',
493 ]
499 ]
494
500
495 def _defaultcacerts(ui):
501 def _defaultcacerts(ui):
496 """return path to default CA certificates or None.
502 """return path to default CA certificates or None.
497
503
498 It is assumed this function is called when the returned certificates
504 It is assumed this function is called when the returned certificates
499 file will actually be used to validate connections. Therefore this
505 file will actually be used to validate connections. Therefore this
500 function may print warnings or debug messages assuming this usage.
506 function may print warnings or debug messages assuming this usage.
501
507
502 We don't print a message when the Python is able to load default
508 We don't print a message when the Python is able to load default
503 CA certs because this scenario is detected at socket connect time.
509 CA certs because this scenario is detected at socket connect time.
504 """
510 """
505 # The "certifi" Python package provides certificates. If it is installed,
511 # The "certifi" Python package provides certificates. If it is installed,
506 # assume the user intends it to be used and use it.
512 # assume the user intends it to be used and use it.
507 try:
513 try:
508 import certifi
514 import certifi
509 certs = certifi.where()
515 certs = certifi.where()
510 ui.debug('using ca certificates from certifi\n')
516 ui.debug('using ca certificates from certifi\n')
511 return certs
517 return certs
512 except ImportError:
518 except ImportError:
513 pass
519 pass
514
520
515 # On Windows, only the modern ssl module is capable of loading the system
521 # On Windows, only the modern ssl module is capable of loading the system
516 # CA certificates. If we're not capable of doing that, emit a warning
522 # CA certificates. If we're not capable of doing that, emit a warning
517 # because we'll get a certificate verification error later and the lack
523 # because we'll get a certificate verification error later and the lack
518 # of loaded CA certificates will be the reason why.
524 # of loaded CA certificates will be the reason why.
519 # Assertion: this code is only called if certificates are being verified.
525 # Assertion: this code is only called if certificates are being verified.
520 if os.name == 'nt':
526 if os.name == 'nt':
521 if not _canloaddefaultcerts:
527 if not _canloaddefaultcerts:
522 ui.warn(_('(unable to load Windows CA certificates; see '
528 ui.warn(_('(unable to load Windows CA certificates; see '
523 'https://mercurial-scm.org/wiki/SecureConnections for '
529 'https://mercurial-scm.org/wiki/SecureConnections for '
524 'how to configure Mercurial to avoid this message)\n'))
530 'how to configure Mercurial to avoid this message)\n'))
525
531
526 return None
532 return None
527
533
528 # Apple's OpenSSL has patches that allow a specially constructed certificate
534 # Apple's OpenSSL has patches that allow a specially constructed certificate
529 # to load the system CA store. If we're running on Apple Python, use this
535 # to load the system CA store. If we're running on Apple Python, use this
530 # trick.
536 # trick.
531 if _plainapplepython():
537 if _plainapplepython():
532 dummycert = os.path.join(os.path.dirname(__file__), 'dummycert.pem')
538 dummycert = os.path.join(os.path.dirname(__file__), 'dummycert.pem')
533 if os.path.exists(dummycert):
539 if os.path.exists(dummycert):
534 return dummycert
540 return dummycert
535
541
536 # The Apple OpenSSL trick isn't available to us. If Python isn't able to
542 # The Apple OpenSSL trick isn't available to us. If Python isn't able to
537 # load system certs, we're out of luck.
543 # load system certs, we're out of luck.
538 if sys.platform == 'darwin':
544 if sys.platform == 'darwin':
539 # FUTURE Consider looking for Homebrew or MacPorts installed certs
545 # FUTURE Consider looking for Homebrew or MacPorts installed certs
540 # files. Also consider exporting the keychain certs to a file during
546 # files. Also consider exporting the keychain certs to a file during
541 # Mercurial install.
547 # Mercurial install.
542 if not _canloaddefaultcerts:
548 if not _canloaddefaultcerts:
543 ui.warn(_('(unable to load CA certificates; see '
549 ui.warn(_('(unable to load CA certificates; see '
544 'https://mercurial-scm.org/wiki/SecureConnections for '
550 'https://mercurial-scm.org/wiki/SecureConnections for '
545 'how to configure Mercurial to avoid this message)\n'))
551 'how to configure Mercurial to avoid this message)\n'))
546 return None
552 return None
547
553
548 # / is writable on Windows. Out of an abundance of caution make sure
554 # / is writable on Windows. Out of an abundance of caution make sure
549 # we're not on Windows because paths from _systemcacerts could be installed
555 # we're not on Windows because paths from _systemcacerts could be installed
550 # by non-admin users.
556 # by non-admin users.
551 assert os.name != 'nt'
557 assert os.name != 'nt'
552
558
553 # Try to find CA certificates in well-known locations. We print a warning
559 # Try to find CA certificates in well-known locations. We print a warning
554 # when using a found file because we don't want too much silent magic
560 # when using a found file because we don't want too much silent magic
555 # for security settings. The expectation is that proper Mercurial
561 # for security settings. The expectation is that proper Mercurial
556 # installs will have the CA certs path defined at install time and the
562 # installs will have the CA certs path defined at install time and the
557 # installer/packager will make an appropriate decision on the user's
563 # installer/packager will make an appropriate decision on the user's
558 # behalf. We only get here and perform this setting as a feature of
564 # behalf. We only get here and perform this setting as a feature of
559 # last resort.
565 # last resort.
560 if not _canloaddefaultcerts:
566 if not _canloaddefaultcerts:
561 for path in _systemcacertpaths:
567 for path in _systemcacertpaths:
562 if os.path.isfile(path):
568 if os.path.isfile(path):
563 ui.warn(_('(using CA certificates from %s; if you see this '
569 ui.warn(_('(using CA certificates from %s; if you see this '
564 'message, your Mercurial install is not properly '
570 'message, your Mercurial install is not properly '
565 'configured; see '
571 'configured; see '
566 'https://mercurial-scm.org/wiki/SecureConnections '
572 'https://mercurial-scm.org/wiki/SecureConnections '
567 'for how to configure Mercurial to avoid this '
573 'for how to configure Mercurial to avoid this '
568 'message)\n') % path)
574 'message)\n') % path)
569 return path
575 return path
570
576
571 ui.warn(_('(unable to load CA certificates; see '
577 ui.warn(_('(unable to load CA certificates; see '
572 'https://mercurial-scm.org/wiki/SecureConnections for '
578 'https://mercurial-scm.org/wiki/SecureConnections for '
573 'how to configure Mercurial to avoid this message)\n'))
579 'how to configure Mercurial to avoid this message)\n'))
574
580
575 return None
581 return None
576
582
577 def validatesocket(sock):
583 def validatesocket(sock):
578 """Validate a socket meets security requiremnets.
584 """Validate a socket meets security requiremnets.
579
585
580 The passed socket must have been created with ``wrapsocket()``.
586 The passed socket must have been created with ``wrapsocket()``.
581 """
587 """
582 host = sock._hgstate['hostname']
588 host = sock._hgstate['hostname']
583 ui = sock._hgstate['ui']
589 ui = sock._hgstate['ui']
584 settings = sock._hgstate['settings']
590 settings = sock._hgstate['settings']
585
591
586 try:
592 try:
587 peercert = sock.getpeercert(True)
593 peercert = sock.getpeercert(True)
588 peercert2 = sock.getpeercert()
594 peercert2 = sock.getpeercert()
589 except AttributeError:
595 except AttributeError:
590 raise error.Abort(_('%s ssl connection error') % host)
596 raise error.Abort(_('%s ssl connection error') % host)
591
597
592 if not peercert:
598 if not peercert:
593 raise error.Abort(_('%s certificate error: '
599 raise error.Abort(_('%s certificate error: '
594 'no certificate received') % host)
600 'no certificate received') % host)
595
601
596 if settings['disablecertverification']:
602 if settings['disablecertverification']:
597 # We don't print the certificate fingerprint because it shouldn't
603 # We don't print the certificate fingerprint because it shouldn't
598 # be necessary: if the user requested certificate verification be
604 # be necessary: if the user requested certificate verification be
599 # disabled, they presumably already saw a message about the inability
605 # disabled, they presumably already saw a message about the inability
600 # to verify the certificate and this message would have printed the
606 # to verify the certificate and this message would have printed the
601 # fingerprint. So printing the fingerprint here adds little to no
607 # fingerprint. So printing the fingerprint here adds little to no
602 # value.
608 # value.
603 ui.warn(_('warning: connection security to %s is disabled per current '
609 ui.warn(_('warning: connection security to %s is disabled per current '
604 'settings; communication is susceptible to eavesdropping '
610 'settings; communication is susceptible to eavesdropping '
605 'and tampering\n') % host)
611 'and tampering\n') % host)
606 return
612 return
607
613
608 # If a certificate fingerprint is pinned, use it and only it to
614 # If a certificate fingerprint is pinned, use it and only it to
609 # validate the remote cert.
615 # validate the remote cert.
610 peerfingerprints = {
616 peerfingerprints = {
611 'sha1': hashlib.sha1(peercert).hexdigest(),
617 'sha1': hashlib.sha1(peercert).hexdigest(),
612 'sha256': hashlib.sha256(peercert).hexdigest(),
618 'sha256': hashlib.sha256(peercert).hexdigest(),
613 'sha512': hashlib.sha512(peercert).hexdigest(),
619 'sha512': hashlib.sha512(peercert).hexdigest(),
614 }
620 }
615
621
616 def fmtfingerprint(s):
622 def fmtfingerprint(s):
617 return ':'.join([s[x:x + 2] for x in range(0, len(s), 2)])
623 return ':'.join([s[x:x + 2] for x in range(0, len(s), 2)])
618
624
619 nicefingerprint = 'sha256:%s' % fmtfingerprint(peerfingerprints['sha256'])
625 nicefingerprint = 'sha256:%s' % fmtfingerprint(peerfingerprints['sha256'])
620
626
621 if settings['certfingerprints']:
627 if settings['certfingerprints']:
622 for hash, fingerprint in settings['certfingerprints']:
628 for hash, fingerprint in settings['certfingerprints']:
623 if peerfingerprints[hash].lower() == fingerprint:
629 if peerfingerprints[hash].lower() == fingerprint:
624 ui.debug('%s certificate matched fingerprint %s:%s\n' %
630 ui.debug('%s certificate matched fingerprint %s:%s\n' %
625 (host, hash, fmtfingerprint(fingerprint)))
631 (host, hash, fmtfingerprint(fingerprint)))
626 return
632 return
627
633
628 # Pinned fingerprint didn't match. This is a fatal error.
634 # Pinned fingerprint didn't match. This is a fatal error.
629 if settings['legacyfingerprint']:
635 if settings['legacyfingerprint']:
630 section = 'hostfingerprint'
636 section = 'hostfingerprint'
631 nice = fmtfingerprint(peerfingerprints['sha1'])
637 nice = fmtfingerprint(peerfingerprints['sha1'])
632 else:
638 else:
633 section = 'hostsecurity'
639 section = 'hostsecurity'
634 nice = '%s:%s' % (hash, fmtfingerprint(peerfingerprints[hash]))
640 nice = '%s:%s' % (hash, fmtfingerprint(peerfingerprints[hash]))
635 raise error.Abort(_('certificate for %s has unexpected '
641 raise error.Abort(_('certificate for %s has unexpected '
636 'fingerprint %s') % (host, nice),
642 'fingerprint %s') % (host, nice),
637 hint=_('check %s configuration') % section)
643 hint=_('check %s configuration') % section)
638
644
639 # Security is enabled but no CAs are loaded. We can't establish trust
645 # Security is enabled but no CAs are loaded. We can't establish trust
640 # for the cert so abort.
646 # for the cert so abort.
641 if not sock._hgstate['caloaded']:
647 if not sock._hgstate['caloaded']:
642 raise error.Abort(
648 raise error.Abort(
643 _('unable to verify security of %s (no loaded CA certificates); '
649 _('unable to verify security of %s (no loaded CA certificates); '
644 'refusing to connect') % host,
650 'refusing to connect') % host,
645 hint=_('see https://mercurial-scm.org/wiki/SecureConnections for '
651 hint=_('see https://mercurial-scm.org/wiki/SecureConnections for '
646 'how to configure Mercurial to avoid this error or set '
652 'how to configure Mercurial to avoid this error or set '
647 'hostsecurity.%s:fingerprints=%s to trust this server') %
653 'hostsecurity.%s:fingerprints=%s to trust this server') %
648 (host, nicefingerprint))
654 (host, nicefingerprint))
649
655
650 msg = _verifycert(peercert2, host)
656 msg = _verifycert(peercert2, host)
651 if msg:
657 if msg:
652 raise error.Abort(_('%s certificate error: %s') % (host, msg),
658 raise error.Abort(_('%s certificate error: %s') % (host, msg),
653 hint=_('set hostsecurity.%s:certfingerprints=%s '
659 hint=_('set hostsecurity.%s:certfingerprints=%s '
654 'config setting or use --insecure to connect '
660 'config setting or use --insecure to connect '
655 'insecurely') %
661 'insecurely') %
656 (host, nicefingerprint))
662 (host, nicefingerprint))
General Comments 0
You need to be logged in to leave comments. Login now