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