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