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