##// END OF EJS Templates
sslutil: move CA file processing into _hostsettings()...
Gregory Szorc -
r29260:70bc9912 default
parent child Browse files
Show More
@@ -1,383 +1,379
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 os
12 import os
13 import ssl
13 import ssl
14 import sys
14 import sys
15
15
16 from .i18n import _
16 from .i18n import _
17 from . import (
17 from . import (
18 error,
18 error,
19 util,
19 util,
20 )
20 )
21
21
22 # Python 2.7.9+ overhauled the built-in SSL/TLS features of Python. It added
22 # Python 2.7.9+ overhauled the built-in SSL/TLS features of Python. It added
23 # support for TLS 1.1, TLS 1.2, SNI, system CA stores, etc. These features are
23 # support for TLS 1.1, TLS 1.2, SNI, system CA stores, etc. These features are
24 # all exposed via the "ssl" module.
24 # all exposed via the "ssl" module.
25 #
25 #
26 # Depending on the version of Python being used, SSL/TLS support is either
26 # Depending on the version of Python being used, SSL/TLS support is either
27 # modern/secure or legacy/insecure. Many operations in this module have
27 # modern/secure or legacy/insecure. Many operations in this module have
28 # separate code paths depending on support in Python.
28 # separate code paths depending on support in Python.
29
29
30 hassni = getattr(ssl, 'HAS_SNI', False)
30 hassni = getattr(ssl, 'HAS_SNI', False)
31
31
32 try:
32 try:
33 OP_NO_SSLv2 = ssl.OP_NO_SSLv2
33 OP_NO_SSLv2 = ssl.OP_NO_SSLv2
34 OP_NO_SSLv3 = ssl.OP_NO_SSLv3
34 OP_NO_SSLv3 = ssl.OP_NO_SSLv3
35 except AttributeError:
35 except AttributeError:
36 OP_NO_SSLv2 = 0x1000000
36 OP_NO_SSLv2 = 0x1000000
37 OP_NO_SSLv3 = 0x2000000
37 OP_NO_SSLv3 = 0x2000000
38
38
39 try:
39 try:
40 # ssl.SSLContext was added in 2.7.9 and presence indicates modern
40 # ssl.SSLContext was added in 2.7.9 and presence indicates modern
41 # SSL/TLS features are available.
41 # SSL/TLS features are available.
42 SSLContext = ssl.SSLContext
42 SSLContext = ssl.SSLContext
43 modernssl = True
43 modernssl = True
44 _canloaddefaultcerts = util.safehasattr(SSLContext, 'load_default_certs')
44 _canloaddefaultcerts = util.safehasattr(SSLContext, 'load_default_certs')
45 except AttributeError:
45 except AttributeError:
46 modernssl = False
46 modernssl = False
47 _canloaddefaultcerts = False
47 _canloaddefaultcerts = False
48
48
49 # We implement SSLContext using the interface from the standard library.
49 # We implement SSLContext using the interface from the standard library.
50 class SSLContext(object):
50 class SSLContext(object):
51 # ssl.wrap_socket gained the "ciphers" named argument in 2.7.
51 # ssl.wrap_socket gained the "ciphers" named argument in 2.7.
52 _supportsciphers = sys.version_info >= (2, 7)
52 _supportsciphers = sys.version_info >= (2, 7)
53
53
54 def __init__(self, protocol):
54 def __init__(self, protocol):
55 # From the public interface of SSLContext
55 # From the public interface of SSLContext
56 self.protocol = protocol
56 self.protocol = protocol
57 self.check_hostname = False
57 self.check_hostname = False
58 self.options = 0
58 self.options = 0
59 self.verify_mode = ssl.CERT_NONE
59 self.verify_mode = ssl.CERT_NONE
60
60
61 # Used by our implementation.
61 # Used by our implementation.
62 self._certfile = None
62 self._certfile = None
63 self._keyfile = None
63 self._keyfile = None
64 self._certpassword = None
64 self._certpassword = None
65 self._cacerts = None
65 self._cacerts = None
66 self._ciphers = None
66 self._ciphers = None
67
67
68 def load_cert_chain(self, certfile, keyfile=None, password=None):
68 def load_cert_chain(self, certfile, keyfile=None, password=None):
69 self._certfile = certfile
69 self._certfile = certfile
70 self._keyfile = keyfile
70 self._keyfile = keyfile
71 self._certpassword = password
71 self._certpassword = password
72
72
73 def load_default_certs(self, purpose=None):
73 def load_default_certs(self, purpose=None):
74 pass
74 pass
75
75
76 def load_verify_locations(self, cafile=None, capath=None, cadata=None):
76 def load_verify_locations(self, cafile=None, capath=None, cadata=None):
77 if capath:
77 if capath:
78 raise error.Abort('capath not supported')
78 raise error.Abort('capath not supported')
79 if cadata:
79 if cadata:
80 raise error.Abort('cadata not supported')
80 raise error.Abort('cadata not supported')
81
81
82 self._cacerts = cafile
82 self._cacerts = cafile
83
83
84 def set_ciphers(self, ciphers):
84 def set_ciphers(self, ciphers):
85 if not self._supportsciphers:
85 if not self._supportsciphers:
86 raise error.Abort('setting ciphers not supported')
86 raise error.Abort('setting ciphers not supported')
87
87
88 self._ciphers = ciphers
88 self._ciphers = ciphers
89
89
90 def wrap_socket(self, socket, server_hostname=None, server_side=False):
90 def wrap_socket(self, socket, server_hostname=None, server_side=False):
91 # server_hostname is unique to SSLContext.wrap_socket and is used
91 # server_hostname is unique to SSLContext.wrap_socket and is used
92 # for SNI in that context. So there's nothing for us to do with it
92 # for SNI in that context. So there's nothing for us to do with it
93 # in this legacy code since we don't support SNI.
93 # in this legacy code since we don't support SNI.
94
94
95 args = {
95 args = {
96 'keyfile': self._keyfile,
96 'keyfile': self._keyfile,
97 'certfile': self._certfile,
97 'certfile': self._certfile,
98 'server_side': server_side,
98 'server_side': server_side,
99 'cert_reqs': self.verify_mode,
99 'cert_reqs': self.verify_mode,
100 'ssl_version': self.protocol,
100 'ssl_version': self.protocol,
101 'ca_certs': self._cacerts,
101 'ca_certs': self._cacerts,
102 }
102 }
103
103
104 if self._supportsciphers:
104 if self._supportsciphers:
105 args['ciphers'] = self._ciphers
105 args['ciphers'] = self._ciphers
106
106
107 return ssl.wrap_socket(socket, **args)
107 return ssl.wrap_socket(socket, **args)
108
108
109 def _hostsettings(ui, hostname):
109 def _hostsettings(ui, hostname):
110 """Obtain security settings for a hostname.
110 """Obtain security settings for a hostname.
111
111
112 Returns a dict of settings relevant to that hostname.
112 Returns a dict of settings relevant to that hostname.
113 """
113 """
114 s = {
114 s = {
115 # List of 2-tuple of (hash algorithm, hash).
115 # List of 2-tuple of (hash algorithm, hash).
116 'certfingerprints': [],
116 'certfingerprints': [],
117 # Path to file containing concatenated CA certs. Used by
118 # SSLContext.load_verify_locations().
119 'cafile': None,
117 # ssl.CERT_* constant used by SSLContext.verify_mode.
120 # ssl.CERT_* constant used by SSLContext.verify_mode.
118 'verifymode': None,
121 'verifymode': None,
119 }
122 }
120
123
121 # Fingerprints from [hostfingerprints] are always SHA-1.
124 # Fingerprints from [hostfingerprints] are always SHA-1.
122 for fingerprint in ui.configlist('hostfingerprints', hostname, []):
125 for fingerprint in ui.configlist('hostfingerprints', hostname, []):
123 fingerprint = fingerprint.replace(':', '').lower()
126 fingerprint = fingerprint.replace(':', '').lower()
124 s['certfingerprints'].append(('sha1', fingerprint))
127 s['certfingerprints'].append(('sha1', fingerprint))
125
128
126 # If a host cert fingerprint is defined, it is the only thing that
129 # If a host cert fingerprint is defined, it is the only thing that
127 # matters. No need to validate CA certs.
130 # matters. No need to validate CA certs.
128 if s['certfingerprints']:
131 if s['certfingerprints']:
129 s['verifymode'] = ssl.CERT_NONE
132 s['verifymode'] = ssl.CERT_NONE
130
133
131 # If --insecure is used, don't take CAs into consideration.
134 # If --insecure is used, don't take CAs into consideration.
132 elif ui.insecureconnections:
135 elif ui.insecureconnections:
133 s['verifymode'] = ssl.CERT_NONE
136 s['verifymode'] = ssl.CERT_NONE
134
137
135 # TODO assert verifymode is not None once we integrate cacert
138 # Try to hook up CA certificate validation unless something above
136 # checking in this function.
139 # makes it not necessary.
140 if s['verifymode'] is None:
141 # Find global certificates file in config.
142 cafile = ui.config('web', 'cacerts')
143
144 if cafile:
145 cafile = util.expandpath(cafile)
146 if not os.path.exists(cafile):
147 raise error.Abort(_('could not find web.cacerts: %s') % cafile)
148 else:
149 # No global CA certs. See if we can load defaults.
150 cafile = _defaultcacerts()
151 if cafile:
152 ui.debug('using %s to enable OS X system CA\n' % cafile)
153
154 s['cafile'] = cafile
155
156 # Require certificate validation if CA certs are being loaded and
157 # verification hasn't been disabled above.
158 if cafile or _canloaddefaultcerts:
159 s['verifymode'] = ssl.CERT_REQUIRED
160 else:
161 # At this point we don't have a fingerprint, aren't being
162 # explicitly insecure, and can't load CA certs. Connecting
163 # at this point is insecure. But we do it for BC reasons.
164 # TODO abort here to make secure by default.
165 s['verifymode'] = ssl.CERT_NONE
166
167 assert s['verifymode'] is not None
137
168
138 return s
169 return s
139
170
140 def _determinecertoptions(ui, settings):
141 """Determine certificate options for a connections.
142
143 Returns a tuple of (cert_reqs, ca_certs).
144 """
145 if settings['verifymode'] == ssl.CERT_NONE:
146 return ssl.CERT_NONE, None
147
148 cacerts = ui.config('web', 'cacerts')
149
150 # If a value is set in the config, validate against a path and load
151 # and require those certs.
152 if cacerts:
153 cacerts = util.expandpath(cacerts)
154 if not os.path.exists(cacerts):
155 raise error.Abort(_('could not find web.cacerts: %s') % cacerts)
156
157 return ssl.CERT_REQUIRED, cacerts
158
159 # No CAs in config. See if we can load defaults.
160 cacerts = _defaultcacerts()
161
162 # We found an alternate CA bundle to use. Load it.
163 if cacerts:
164 ui.debug('using %s to enable OS X system CA\n' % cacerts)
165 ui.setconfig('web', 'cacerts', cacerts, 'defaultcacerts')
166 return ssl.CERT_REQUIRED, cacerts
167
168 # FUTURE this can disappear once wrapsocket() is secure by default.
169 if _canloaddefaultcerts:
170 return ssl.CERT_REQUIRED, None
171
172 return ssl.CERT_NONE, None
173
174 def wrapsocket(sock, keyfile, certfile, ui, serverhostname=None):
171 def wrapsocket(sock, keyfile, certfile, ui, serverhostname=None):
175 """Add SSL/TLS to a socket.
172 """Add SSL/TLS to a socket.
176
173
177 This is a glorified wrapper for ``ssl.wrap_socket()``. It makes sane
174 This is a glorified wrapper for ``ssl.wrap_socket()``. It makes sane
178 choices based on what security options are available.
175 choices based on what security options are available.
179
176
180 In addition to the arguments supported by ``ssl.wrap_socket``, we allow
177 In addition to the arguments supported by ``ssl.wrap_socket``, we allow
181 the following additional arguments:
178 the following additional arguments:
182
179
183 * serverhostname - The expected hostname of the remote server. If the
180 * serverhostname - The expected hostname of the remote server. If the
184 server (and client) support SNI, this tells the server which certificate
181 server (and client) support SNI, this tells the server which certificate
185 to use.
182 to use.
186 """
183 """
187 if not serverhostname:
184 if not serverhostname:
188 raise error.Abort('serverhostname argument is required')
185 raise error.Abort('serverhostname argument is required')
189
186
190 settings = _hostsettings(ui, serverhostname)
187 settings = _hostsettings(ui, serverhostname)
191 cert_reqs, ca_certs = _determinecertoptions(ui, settings)
192
188
193 # Despite its name, PROTOCOL_SSLv23 selects the highest protocol
189 # Despite its name, PROTOCOL_SSLv23 selects the highest protocol
194 # that both ends support, including TLS protocols. On legacy stacks,
190 # that both ends support, including TLS protocols. On legacy stacks,
195 # the highest it likely goes in TLS 1.0. On modern stacks, it can
191 # the highest it likely goes in TLS 1.0. On modern stacks, it can
196 # support TLS 1.2.
192 # support TLS 1.2.
197 #
193 #
198 # The PROTOCOL_TLSv* constants select a specific TLS version
194 # The PROTOCOL_TLSv* constants select a specific TLS version
199 # only (as opposed to multiple versions). So the method for
195 # only (as opposed to multiple versions). So the method for
200 # supporting multiple TLS versions is to use PROTOCOL_SSLv23 and
196 # supporting multiple TLS versions is to use PROTOCOL_SSLv23 and
201 # disable protocols via SSLContext.options and OP_NO_* constants.
197 # disable protocols via SSLContext.options and OP_NO_* constants.
202 # However, SSLContext.options doesn't work unless we have the
198 # However, SSLContext.options doesn't work unless we have the
203 # full/real SSLContext available to us.
199 # full/real SSLContext available to us.
204 #
200 #
205 # SSLv2 and SSLv3 are broken. We ban them outright.
201 # SSLv2 and SSLv3 are broken. We ban them outright.
206 if modernssl:
202 if modernssl:
207 protocol = ssl.PROTOCOL_SSLv23
203 protocol = ssl.PROTOCOL_SSLv23
208 else:
204 else:
209 protocol = ssl.PROTOCOL_TLSv1
205 protocol = ssl.PROTOCOL_TLSv1
210
206
211 # TODO use ssl.create_default_context() on modernssl.
207 # TODO use ssl.create_default_context() on modernssl.
212 sslcontext = SSLContext(protocol)
208 sslcontext = SSLContext(protocol)
213
209
214 # This is a no-op on old Python.
210 # This is a no-op on old Python.
215 sslcontext.options |= OP_NO_SSLv2 | OP_NO_SSLv3
211 sslcontext.options |= OP_NO_SSLv2 | OP_NO_SSLv3
216
212
217 # This still works on our fake SSLContext.
213 # This still works on our fake SSLContext.
218 sslcontext.verify_mode = cert_reqs
214 sslcontext.verify_mode = settings['verifymode']
219
215
220 if certfile is not None:
216 if certfile is not None:
221 def password():
217 def password():
222 f = keyfile or certfile
218 f = keyfile or certfile
223 return ui.getpass(_('passphrase for %s: ') % f, '')
219 return ui.getpass(_('passphrase for %s: ') % f, '')
224 sslcontext.load_cert_chain(certfile, keyfile, password)
220 sslcontext.load_cert_chain(certfile, keyfile, password)
225
221
226 if ca_certs is not None:
222 if settings['cafile'] is not None:
227 sslcontext.load_verify_locations(cafile=ca_certs)
223 sslcontext.load_verify_locations(cafile=settings['cafile'])
228 caloaded = True
224 caloaded = True
229 else:
225 else:
230 # This is a no-op on old Python.
226 # This is a no-op on old Python.
231 sslcontext.load_default_certs()
227 sslcontext.load_default_certs()
232 caloaded = _canloaddefaultcerts
228 caloaded = _canloaddefaultcerts
233
229
234 sslsocket = sslcontext.wrap_socket(sock, server_hostname=serverhostname)
230 sslsocket = sslcontext.wrap_socket(sock, server_hostname=serverhostname)
235 # check if wrap_socket failed silently because socket had been
231 # check if wrap_socket failed silently because socket had been
236 # closed
232 # closed
237 # - see http://bugs.python.org/issue13721
233 # - see http://bugs.python.org/issue13721
238 if not sslsocket.cipher():
234 if not sslsocket.cipher():
239 raise error.Abort(_('ssl connection failed'))
235 raise error.Abort(_('ssl connection failed'))
240
236
241 sslsocket._hgstate = {
237 sslsocket._hgstate = {
242 'caloaded': caloaded,
238 'caloaded': caloaded,
243 'hostname': serverhostname,
239 'hostname': serverhostname,
244 'settings': settings,
240 'settings': settings,
245 'ui': ui,
241 'ui': ui,
246 }
242 }
247
243
248 return sslsocket
244 return sslsocket
249
245
250 def _verifycert(cert, hostname):
246 def _verifycert(cert, hostname):
251 '''Verify that cert (in socket.getpeercert() format) matches hostname.
247 '''Verify that cert (in socket.getpeercert() format) matches hostname.
252 CRLs is not handled.
248 CRLs is not handled.
253
249
254 Returns error message if any problems are found and None on success.
250 Returns error message if any problems are found and None on success.
255 '''
251 '''
256 if not cert:
252 if not cert:
257 return _('no certificate received')
253 return _('no certificate received')
258 dnsname = hostname.lower()
254 dnsname = hostname.lower()
259 def matchdnsname(certname):
255 def matchdnsname(certname):
260 return (certname == dnsname or
256 return (certname == dnsname or
261 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1])
257 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1])
262
258
263 san = cert.get('subjectAltName', [])
259 san = cert.get('subjectAltName', [])
264 if san:
260 if san:
265 certnames = [value.lower() for key, value in san if key == 'DNS']
261 certnames = [value.lower() for key, value in san if key == 'DNS']
266 for name in certnames:
262 for name in certnames:
267 if matchdnsname(name):
263 if matchdnsname(name):
268 return None
264 return None
269 if certnames:
265 if certnames:
270 return _('certificate is for %s') % ', '.join(certnames)
266 return _('certificate is for %s') % ', '.join(certnames)
271
267
272 # subject is only checked when subjectAltName is empty
268 # subject is only checked when subjectAltName is empty
273 for s in cert.get('subject', []):
269 for s in cert.get('subject', []):
274 key, value = s[0]
270 key, value = s[0]
275 if key == 'commonName':
271 if key == 'commonName':
276 try:
272 try:
277 # 'subject' entries are unicode
273 # 'subject' entries are unicode
278 certname = value.lower().encode('ascii')
274 certname = value.lower().encode('ascii')
279 except UnicodeEncodeError:
275 except UnicodeEncodeError:
280 return _('IDN in certificate not supported')
276 return _('IDN in certificate not supported')
281 if matchdnsname(certname):
277 if matchdnsname(certname):
282 return None
278 return None
283 return _('certificate is for %s') % certname
279 return _('certificate is for %s') % certname
284 return _('no commonName or subjectAltName found in certificate')
280 return _('no commonName or subjectAltName found in certificate')
285
281
286
282
287 # CERT_REQUIRED means fetch the cert from the server all the time AND
283 # CERT_REQUIRED means fetch the cert from the server all the time AND
288 # validate it against the CA store provided in web.cacerts.
284 # validate it against the CA store provided in web.cacerts.
289
285
290 def _plainapplepython():
286 def _plainapplepython():
291 """return true if this seems to be a pure Apple Python that
287 """return true if this seems to be a pure Apple Python that
292 * is unfrozen and presumably has the whole mercurial module in the file
288 * is unfrozen and presumably has the whole mercurial module in the file
293 system
289 system
294 * presumably is an Apple Python that uses Apple OpenSSL which has patches
290 * presumably is an Apple Python that uses Apple OpenSSL which has patches
295 for using system certificate store CAs in addition to the provided
291 for using system certificate store CAs in addition to the provided
296 cacerts file
292 cacerts file
297 """
293 """
298 if sys.platform != 'darwin' or util.mainfrozen() or not sys.executable:
294 if sys.platform != 'darwin' or util.mainfrozen() or not sys.executable:
299 return False
295 return False
300 exe = os.path.realpath(sys.executable).lower()
296 exe = os.path.realpath(sys.executable).lower()
301 return (exe.startswith('/usr/bin/python') or
297 return (exe.startswith('/usr/bin/python') or
302 exe.startswith('/system/library/frameworks/python.framework/'))
298 exe.startswith('/system/library/frameworks/python.framework/'))
303
299
304 def _defaultcacerts():
300 def _defaultcacerts():
305 """return path to default CA certificates or None."""
301 """return path to default CA certificates or None."""
306 if _plainapplepython():
302 if _plainapplepython():
307 dummycert = os.path.join(os.path.dirname(__file__), 'dummycert.pem')
303 dummycert = os.path.join(os.path.dirname(__file__), 'dummycert.pem')
308 if os.path.exists(dummycert):
304 if os.path.exists(dummycert):
309 return dummycert
305 return dummycert
310
306
311 return None
307 return None
312
308
313 def validatesocket(sock, strict=False):
309 def validatesocket(sock, strict=False):
314 """Validate a socket meets security requiremnets.
310 """Validate a socket meets security requiremnets.
315
311
316 The passed socket must have been created with ``wrapsocket()``.
312 The passed socket must have been created with ``wrapsocket()``.
317 """
313 """
318 host = sock._hgstate['hostname']
314 host = sock._hgstate['hostname']
319 ui = sock._hgstate['ui']
315 ui = sock._hgstate['ui']
320 settings = sock._hgstate['settings']
316 settings = sock._hgstate['settings']
321
317
322 try:
318 try:
323 peercert = sock.getpeercert(True)
319 peercert = sock.getpeercert(True)
324 peercert2 = sock.getpeercert()
320 peercert2 = sock.getpeercert()
325 except AttributeError:
321 except AttributeError:
326 raise error.Abort(_('%s ssl connection error') % host)
322 raise error.Abort(_('%s ssl connection error') % host)
327
323
328 if not peercert:
324 if not peercert:
329 raise error.Abort(_('%s certificate error: '
325 raise error.Abort(_('%s certificate error: '
330 'no certificate received') % host)
326 'no certificate received') % host)
331
327
332 # If a certificate fingerprint is pinned, use it and only it to
328 # If a certificate fingerprint is pinned, use it and only it to
333 # validate the remote cert.
329 # validate the remote cert.
334 peerfingerprint = util.sha1(peercert).hexdigest()
330 peerfingerprint = util.sha1(peercert).hexdigest()
335 nicefingerprint = ":".join([peerfingerprint[x:x + 2]
331 nicefingerprint = ":".join([peerfingerprint[x:x + 2]
336 for x in xrange(0, len(peerfingerprint), 2)])
332 for x in xrange(0, len(peerfingerprint), 2)])
337 if settings['certfingerprints']:
333 if settings['certfingerprints']:
338 fingerprintmatch = False
334 fingerprintmatch = False
339 for hash, fingerprint in settings['certfingerprints']:
335 for hash, fingerprint in settings['certfingerprints']:
340 if peerfingerprint.lower() == fingerprint:
336 if peerfingerprint.lower() == fingerprint:
341 fingerprintmatch = True
337 fingerprintmatch = True
342 break
338 break
343 if not fingerprintmatch:
339 if not fingerprintmatch:
344 raise error.Abort(_('certificate for %s has unexpected '
340 raise error.Abort(_('certificate for %s has unexpected '
345 'fingerprint %s') % (host, nicefingerprint),
341 'fingerprint %s') % (host, nicefingerprint),
346 hint=_('check hostfingerprint configuration'))
342 hint=_('check hostfingerprint configuration'))
347 ui.debug('%s certificate matched fingerprint %s\n' %
343 ui.debug('%s certificate matched fingerprint %s\n' %
348 (host, nicefingerprint))
344 (host, nicefingerprint))
349 return
345 return
350
346
351 # If insecure connections were explicitly requested via --insecure,
347 # If insecure connections were explicitly requested via --insecure,
352 # print a warning and do no verification.
348 # print a warning and do no verification.
353 #
349 #
354 # It may seem odd that this is checked *after* host fingerprint pinning.
350 # It may seem odd that this is checked *after* host fingerprint pinning.
355 # This is for backwards compatibility (for now). The message is also
351 # This is for backwards compatibility (for now). The message is also
356 # the same as below for BC.
352 # the same as below for BC.
357 if ui.insecureconnections:
353 if ui.insecureconnections:
358 ui.warn(_('warning: %s certificate with fingerprint %s not '
354 ui.warn(_('warning: %s certificate with fingerprint %s not '
359 'verified (check hostfingerprints or web.cacerts '
355 'verified (check hostfingerprints or web.cacerts '
360 'config setting)\n') %
356 'config setting)\n') %
361 (host, nicefingerprint))
357 (host, nicefingerprint))
362 return
358 return
363
359
364 if not sock._hgstate['caloaded']:
360 if not sock._hgstate['caloaded']:
365 if strict:
361 if strict:
366 raise error.Abort(_('%s certificate with fingerprint %s not '
362 raise error.Abort(_('%s certificate with fingerprint %s not '
367 'verified') % (host, nicefingerprint),
363 'verified') % (host, nicefingerprint),
368 hint=_('check hostfingerprints or '
364 hint=_('check hostfingerprints or '
369 'web.cacerts config setting'))
365 'web.cacerts config setting'))
370 else:
366 else:
371 ui.warn(_('warning: %s certificate with fingerprint %s '
367 ui.warn(_('warning: %s certificate with fingerprint %s '
372 'not verified (check hostfingerprints or '
368 'not verified (check hostfingerprints or '
373 'web.cacerts config setting)\n') %
369 'web.cacerts config setting)\n') %
374 (host, nicefingerprint))
370 (host, nicefingerprint))
375
371
376 return
372 return
377
373
378 msg = _verifycert(peercert2, host)
374 msg = _verifycert(peercert2, host)
379 if msg:
375 if msg:
380 raise error.Abort(_('%s certificate error: %s') % (host, msg),
376 raise error.Abort(_('%s certificate error: %s') % (host, msg),
381 hint=_('configure hostfingerprint %s or use '
377 hint=_('configure hostfingerprint %s or use '
382 '--insecure to connect insecurely') %
378 '--insecure to connect insecurely') %
383 nicefingerprint)
379 nicefingerprint)
General Comments 0
You need to be logged in to leave comments. Login now