##// END OF EJS Templates
sslutil: use a dict for hanging hg state off the wrapped socket...
Gregory Szorc -
r29225:b115eed1 default
parent child Browse files
Show More
@@ -1,363 +1,365 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 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 wrapsocket(sock, keyfile, certfile, ui, cert_reqs=ssl.CERT_NONE,
109 def wrapsocket(sock, keyfile, certfile, ui, cert_reqs=ssl.CERT_NONE,
110 ca_certs=None, serverhostname=None):
110 ca_certs=None, serverhostname=None):
111 """Add SSL/TLS to a socket.
111 """Add SSL/TLS to a socket.
112
112
113 This is a glorified wrapper for ``ssl.wrap_socket()``. It makes sane
113 This is a glorified wrapper for ``ssl.wrap_socket()``. It makes sane
114 choices based on what security options are available.
114 choices based on what security options are available.
115
115
116 In addition to the arguments supported by ``ssl.wrap_socket``, we allow
116 In addition to the arguments supported by ``ssl.wrap_socket``, we allow
117 the following additional arguments:
117 the following additional arguments:
118
118
119 * serverhostname - The expected hostname of the remote server. If the
119 * serverhostname - The expected hostname of the remote server. If the
120 server (and client) support SNI, this tells the server which certificate
120 server (and client) support SNI, this tells the server which certificate
121 to use.
121 to use.
122 """
122 """
123 if not serverhostname:
123 if not serverhostname:
124 raise error.Abort('serverhostname argument is required')
124 raise error.Abort('serverhostname argument is required')
125
125
126 # Despite its name, PROTOCOL_SSLv23 selects the highest protocol
126 # Despite its name, PROTOCOL_SSLv23 selects the highest protocol
127 # that both ends support, including TLS protocols. On legacy stacks,
127 # that both ends support, including TLS protocols. On legacy stacks,
128 # the highest it likely goes in TLS 1.0. On modern stacks, it can
128 # the highest it likely goes in TLS 1.0. On modern stacks, it can
129 # support TLS 1.2.
129 # support TLS 1.2.
130 #
130 #
131 # The PROTOCOL_TLSv* constants select a specific TLS version
131 # The PROTOCOL_TLSv* constants select a specific TLS version
132 # only (as opposed to multiple versions). So the method for
132 # only (as opposed to multiple versions). So the method for
133 # supporting multiple TLS versions is to use PROTOCOL_SSLv23 and
133 # supporting multiple TLS versions is to use PROTOCOL_SSLv23 and
134 # disable protocols via SSLContext.options and OP_NO_* constants.
134 # disable protocols via SSLContext.options and OP_NO_* constants.
135 # However, SSLContext.options doesn't work unless we have the
135 # However, SSLContext.options doesn't work unless we have the
136 # full/real SSLContext available to us.
136 # full/real SSLContext available to us.
137 #
137 #
138 # SSLv2 and SSLv3 are broken. We ban them outright.
138 # SSLv2 and SSLv3 are broken. We ban them outright.
139 if modernssl:
139 if modernssl:
140 protocol = ssl.PROTOCOL_SSLv23
140 protocol = ssl.PROTOCOL_SSLv23
141 else:
141 else:
142 protocol = ssl.PROTOCOL_TLSv1
142 protocol = ssl.PROTOCOL_TLSv1
143
143
144 # TODO use ssl.create_default_context() on modernssl.
144 # TODO use ssl.create_default_context() on modernssl.
145 sslcontext = SSLContext(protocol)
145 sslcontext = SSLContext(protocol)
146
146
147 # This is a no-op on old Python.
147 # This is a no-op on old Python.
148 sslcontext.options |= OP_NO_SSLv2 | OP_NO_SSLv3
148 sslcontext.options |= OP_NO_SSLv2 | OP_NO_SSLv3
149
149
150 # This still works on our fake SSLContext.
150 # This still works on our fake SSLContext.
151 sslcontext.verify_mode = cert_reqs
151 sslcontext.verify_mode = cert_reqs
152
152
153 if certfile is not None:
153 if certfile is not None:
154 def password():
154 def password():
155 f = keyfile or certfile
155 f = keyfile or certfile
156 return ui.getpass(_('passphrase for %s: ') % f, '')
156 return ui.getpass(_('passphrase for %s: ') % f, '')
157 sslcontext.load_cert_chain(certfile, keyfile, password)
157 sslcontext.load_cert_chain(certfile, keyfile, password)
158
158
159 if ca_certs is not None:
159 if ca_certs is not None:
160 sslcontext.load_verify_locations(cafile=ca_certs)
160 sslcontext.load_verify_locations(cafile=ca_certs)
161 caloaded = True
161 caloaded = True
162 else:
162 else:
163 # This is a no-op on old Python.
163 # This is a no-op on old Python.
164 sslcontext.load_default_certs()
164 sslcontext.load_default_certs()
165 caloaded = _canloaddefaultcerts
165 caloaded = _canloaddefaultcerts
166
166
167 sslsocket = sslcontext.wrap_socket(sock, server_hostname=serverhostname)
167 sslsocket = sslcontext.wrap_socket(sock, server_hostname=serverhostname)
168 # check if wrap_socket failed silently because socket had been
168 # check if wrap_socket failed silently because socket had been
169 # closed
169 # closed
170 # - see http://bugs.python.org/issue13721
170 # - see http://bugs.python.org/issue13721
171 if not sslsocket.cipher():
171 if not sslsocket.cipher():
172 raise error.Abort(_('ssl connection failed'))
172 raise error.Abort(_('ssl connection failed'))
173
173
174 sslsocket._hgcaloaded = caloaded
174 sslsocket._hgstate = {
175 'caloaded': caloaded,
176 }
175
177
176 return sslsocket
178 return sslsocket
177
179
178 def _verifycert(cert, hostname):
180 def _verifycert(cert, hostname):
179 '''Verify that cert (in socket.getpeercert() format) matches hostname.
181 '''Verify that cert (in socket.getpeercert() format) matches hostname.
180 CRLs is not handled.
182 CRLs is not handled.
181
183
182 Returns error message if any problems are found and None on success.
184 Returns error message if any problems are found and None on success.
183 '''
185 '''
184 if not cert:
186 if not cert:
185 return _('no certificate received')
187 return _('no certificate received')
186 dnsname = hostname.lower()
188 dnsname = hostname.lower()
187 def matchdnsname(certname):
189 def matchdnsname(certname):
188 return (certname == dnsname or
190 return (certname == dnsname or
189 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1])
191 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1])
190
192
191 san = cert.get('subjectAltName', [])
193 san = cert.get('subjectAltName', [])
192 if san:
194 if san:
193 certnames = [value.lower() for key, value in san if key == 'DNS']
195 certnames = [value.lower() for key, value in san if key == 'DNS']
194 for name in certnames:
196 for name in certnames:
195 if matchdnsname(name):
197 if matchdnsname(name):
196 return None
198 return None
197 if certnames:
199 if certnames:
198 return _('certificate is for %s') % ', '.join(certnames)
200 return _('certificate is for %s') % ', '.join(certnames)
199
201
200 # subject is only checked when subjectAltName is empty
202 # subject is only checked when subjectAltName is empty
201 for s in cert.get('subject', []):
203 for s in cert.get('subject', []):
202 key, value = s[0]
204 key, value = s[0]
203 if key == 'commonName':
205 if key == 'commonName':
204 try:
206 try:
205 # 'subject' entries are unicode
207 # 'subject' entries are unicode
206 certname = value.lower().encode('ascii')
208 certname = value.lower().encode('ascii')
207 except UnicodeEncodeError:
209 except UnicodeEncodeError:
208 return _('IDN in certificate not supported')
210 return _('IDN in certificate not supported')
209 if matchdnsname(certname):
211 if matchdnsname(certname):
210 return None
212 return None
211 return _('certificate is for %s') % certname
213 return _('certificate is for %s') % certname
212 return _('no commonName or subjectAltName found in certificate')
214 return _('no commonName or subjectAltName found in certificate')
213
215
214
216
215 # CERT_REQUIRED means fetch the cert from the server all the time AND
217 # CERT_REQUIRED means fetch the cert from the server all the time AND
216 # validate it against the CA store provided in web.cacerts.
218 # validate it against the CA store provided in web.cacerts.
217
219
218 def _plainapplepython():
220 def _plainapplepython():
219 """return true if this seems to be a pure Apple Python that
221 """return true if this seems to be a pure Apple Python that
220 * is unfrozen and presumably has the whole mercurial module in the file
222 * is unfrozen and presumably has the whole mercurial module in the file
221 system
223 system
222 * presumably is an Apple Python that uses Apple OpenSSL which has patches
224 * presumably is an Apple Python that uses Apple OpenSSL which has patches
223 for using system certificate store CAs in addition to the provided
225 for using system certificate store CAs in addition to the provided
224 cacerts file
226 cacerts file
225 """
227 """
226 if sys.platform != 'darwin' or util.mainfrozen() or not sys.executable:
228 if sys.platform != 'darwin' or util.mainfrozen() or not sys.executable:
227 return False
229 return False
228 exe = os.path.realpath(sys.executable).lower()
230 exe = os.path.realpath(sys.executable).lower()
229 return (exe.startswith('/usr/bin/python') or
231 return (exe.startswith('/usr/bin/python') or
230 exe.startswith('/system/library/frameworks/python.framework/'))
232 exe.startswith('/system/library/frameworks/python.framework/'))
231
233
232 def _defaultcacerts():
234 def _defaultcacerts():
233 """return path to default CA certificates or None."""
235 """return path to default CA certificates or None."""
234 if _plainapplepython():
236 if _plainapplepython():
235 dummycert = os.path.join(os.path.dirname(__file__), 'dummycert.pem')
237 dummycert = os.path.join(os.path.dirname(__file__), 'dummycert.pem')
236 if os.path.exists(dummycert):
238 if os.path.exists(dummycert):
237 return dummycert
239 return dummycert
238
240
239 return None
241 return None
240
242
241 def sslkwargs(ui, host):
243 def sslkwargs(ui, host):
242 """Determine arguments to pass to wrapsocket().
244 """Determine arguments to pass to wrapsocket().
243
245
244 ``host`` is the hostname being connected to.
246 ``host`` is the hostname being connected to.
245 """
247 """
246 kws = {'ui': ui}
248 kws = {'ui': ui}
247
249
248 # If a host key fingerprint is on file, it is the only thing that matters
250 # If a host key fingerprint is on file, it is the only thing that matters
249 # and CA certs don't come into play.
251 # and CA certs don't come into play.
250 hostfingerprint = ui.config('hostfingerprints', host)
252 hostfingerprint = ui.config('hostfingerprints', host)
251 if hostfingerprint:
253 if hostfingerprint:
252 return kws
254 return kws
253
255
254 # The code below sets up CA verification arguments. If --insecure is
256 # The code below sets up CA verification arguments. If --insecure is
255 # used, we don't take CAs into consideration, so return early.
257 # used, we don't take CAs into consideration, so return early.
256 if ui.insecureconnections:
258 if ui.insecureconnections:
257 return kws
259 return kws
258
260
259 cacerts = ui.config('web', 'cacerts')
261 cacerts = ui.config('web', 'cacerts')
260
262
261 # If a value is set in the config, validate against a path and load
263 # If a value is set in the config, validate against a path and load
262 # and require those certs.
264 # and require those certs.
263 if cacerts:
265 if cacerts:
264 cacerts = util.expandpath(cacerts)
266 cacerts = util.expandpath(cacerts)
265 if not os.path.exists(cacerts):
267 if not os.path.exists(cacerts):
266 raise error.Abort(_('could not find web.cacerts: %s') % cacerts)
268 raise error.Abort(_('could not find web.cacerts: %s') % cacerts)
267
269
268 kws.update({'ca_certs': cacerts,
270 kws.update({'ca_certs': cacerts,
269 'cert_reqs': ssl.CERT_REQUIRED})
271 'cert_reqs': ssl.CERT_REQUIRED})
270 return kws
272 return kws
271
273
272 # No CAs in config. See if we can load defaults.
274 # No CAs in config. See if we can load defaults.
273 cacerts = _defaultcacerts()
275 cacerts = _defaultcacerts()
274
276
275 # We found an alternate CA bundle to use. Load it.
277 # We found an alternate CA bundle to use. Load it.
276 if cacerts:
278 if cacerts:
277 ui.debug('using %s to enable OS X system CA\n' % cacerts)
279 ui.debug('using %s to enable OS X system CA\n' % cacerts)
278 ui.setconfig('web', 'cacerts', cacerts, 'defaultcacerts')
280 ui.setconfig('web', 'cacerts', cacerts, 'defaultcacerts')
279 kws.update({'ca_certs': cacerts,
281 kws.update({'ca_certs': cacerts,
280 'cert_reqs': ssl.CERT_REQUIRED})
282 'cert_reqs': ssl.CERT_REQUIRED})
281 return kws
283 return kws
282
284
283 # FUTURE this can disappear once wrapsocket() is secure by default.
285 # FUTURE this can disappear once wrapsocket() is secure by default.
284 if _canloaddefaultcerts:
286 if _canloaddefaultcerts:
285 kws['cert_reqs'] = ssl.CERT_REQUIRED
287 kws['cert_reqs'] = ssl.CERT_REQUIRED
286 return kws
288 return kws
287
289
288 return kws
290 return kws
289
291
290 class validator(object):
292 class validator(object):
291 def __init__(self, ui, host):
293 def __init__(self, ui, host):
292 self.ui = ui
294 self.ui = ui
293 self.host = host
295 self.host = host
294
296
295 def __call__(self, sock, strict=False):
297 def __call__(self, sock, strict=False):
296 host = self.host
298 host = self.host
297
299
298 if not sock.cipher(): # work around http://bugs.python.org/issue13721
300 if not sock.cipher(): # work around http://bugs.python.org/issue13721
299 raise error.Abort(_('%s ssl connection error') % host)
301 raise error.Abort(_('%s ssl connection error') % host)
300 try:
302 try:
301 peercert = sock.getpeercert(True)
303 peercert = sock.getpeercert(True)
302 peercert2 = sock.getpeercert()
304 peercert2 = sock.getpeercert()
303 except AttributeError:
305 except AttributeError:
304 raise error.Abort(_('%s ssl connection error') % host)
306 raise error.Abort(_('%s ssl connection error') % host)
305
307
306 if not peercert:
308 if not peercert:
307 raise error.Abort(_('%s certificate error: '
309 raise error.Abort(_('%s certificate error: '
308 'no certificate received') % host)
310 'no certificate received') % host)
309
311
310 # If a certificate fingerprint is pinned, use it and only it to
312 # If a certificate fingerprint is pinned, use it and only it to
311 # validate the remote cert.
313 # validate the remote cert.
312 hostfingerprints = self.ui.configlist('hostfingerprints', host)
314 hostfingerprints = self.ui.configlist('hostfingerprints', host)
313 peerfingerprint = util.sha1(peercert).hexdigest()
315 peerfingerprint = util.sha1(peercert).hexdigest()
314 nicefingerprint = ":".join([peerfingerprint[x:x + 2]
316 nicefingerprint = ":".join([peerfingerprint[x:x + 2]
315 for x in xrange(0, len(peerfingerprint), 2)])
317 for x in xrange(0, len(peerfingerprint), 2)])
316 if hostfingerprints:
318 if hostfingerprints:
317 fingerprintmatch = False
319 fingerprintmatch = False
318 for hostfingerprint in hostfingerprints:
320 for hostfingerprint in hostfingerprints:
319 if peerfingerprint.lower() == \
321 if peerfingerprint.lower() == \
320 hostfingerprint.replace(':', '').lower():
322 hostfingerprint.replace(':', '').lower():
321 fingerprintmatch = True
323 fingerprintmatch = True
322 break
324 break
323 if not fingerprintmatch:
325 if not fingerprintmatch:
324 raise error.Abort(_('certificate for %s has unexpected '
326 raise error.Abort(_('certificate for %s has unexpected '
325 'fingerprint %s') % (host, nicefingerprint),
327 'fingerprint %s') % (host, nicefingerprint),
326 hint=_('check hostfingerprint configuration'))
328 hint=_('check hostfingerprint configuration'))
327 self.ui.debug('%s certificate matched fingerprint %s\n' %
329 self.ui.debug('%s certificate matched fingerprint %s\n' %
328 (host, nicefingerprint))
330 (host, nicefingerprint))
329 return
331 return
330
332
331 # If insecure connections were explicitly requested via --insecure,
333 # If insecure connections were explicitly requested via --insecure,
332 # print a warning and do no verification.
334 # print a warning and do no verification.
333 #
335 #
334 # It may seem odd that this is checked *after* host fingerprint pinning.
336 # It may seem odd that this is checked *after* host fingerprint pinning.
335 # This is for backwards compatibility (for now). The message is also
337 # This is for backwards compatibility (for now). The message is also
336 # the same as below for BC.
338 # the same as below for BC.
337 if self.ui.insecureconnections:
339 if self.ui.insecureconnections:
338 self.ui.warn(_('warning: %s certificate with fingerprint %s not '
340 self.ui.warn(_('warning: %s certificate with fingerprint %s not '
339 'verified (check hostfingerprints or web.cacerts '
341 'verified (check hostfingerprints or web.cacerts '
340 'config setting)\n') %
342 'config setting)\n') %
341 (host, nicefingerprint))
343 (host, nicefingerprint))
342 return
344 return
343
345
344 if not sock._hgcaloaded:
346 if not sock._hgstate['caloaded']:
345 if strict:
347 if strict:
346 raise error.Abort(_('%s certificate with fingerprint %s not '
348 raise error.Abort(_('%s certificate with fingerprint %s not '
347 'verified') % (host, nicefingerprint),
349 'verified') % (host, nicefingerprint),
348 hint=_('check hostfingerprints or '
350 hint=_('check hostfingerprints or '
349 'web.cacerts config setting'))
351 'web.cacerts config setting'))
350 else:
352 else:
351 self.ui.warn(_('warning: %s certificate with fingerprint %s '
353 self.ui.warn(_('warning: %s certificate with fingerprint %s '
352 'not verified (check hostfingerprints or '
354 'not verified (check hostfingerprints or '
353 'web.cacerts config setting)\n') %
355 'web.cacerts config setting)\n') %
354 (host, nicefingerprint))
356 (host, nicefingerprint))
355
357
356 return
358 return
357
359
358 msg = _verifycert(peercert2, host)
360 msg = _verifycert(peercert2, host)
359 if msg:
361 if msg:
360 raise error.Abort(_('%s certificate error: %s') % (host, msg),
362 raise error.Abort(_('%s certificate error: %s') % (host, msg),
361 hint=_('configure hostfingerprint %s or use '
363 hint=_('configure hostfingerprint %s or use '
362 '--insecure to connect insecurely') %
364 '--insecure to connect insecurely') %
363 nicefingerprint)
365 nicefingerprint)
General Comments 0
You need to be logged in to leave comments. Login now