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