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