##// END OF EJS Templates
sslutil: don't access message attribute in exception (issue5285)...
Gregory Szorc -
r29460:a7d1532b stable
parent child Browse files
Show More
@@ -1,382 +1,382 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 re
13 import re
14 import ssl
14 import ssl
15 import sys
15 import sys
16
16
17 from .i18n import _
17 from .i18n import _
18 from . import (
18 from . import (
19 error,
19 error,
20 util,
20 util,
21 )
21 )
22
22
23 # Python 2.7.9+ overhauled the built-in SSL/TLS features of Python. It added
23 # Python 2.7.9+ overhauled the built-in SSL/TLS features of Python. It added
24 # support for TLS 1.1, TLS 1.2, SNI, system CA stores, etc. These features are
24 # support for TLS 1.1, TLS 1.2, SNI, system CA stores, etc. These features are
25 # all exposed via the "ssl" module.
25 # all exposed via the "ssl" module.
26 #
26 #
27 # Depending on the version of Python being used, SSL/TLS support is either
27 # Depending on the version of Python being used, SSL/TLS support is either
28 # modern/secure or legacy/insecure. Many operations in this module have
28 # modern/secure or legacy/insecure. Many operations in this module have
29 # separate code paths depending on support in Python.
29 # separate code paths depending on support in Python.
30
30
31 hassni = getattr(ssl, 'HAS_SNI', False)
31 hassni = getattr(ssl, 'HAS_SNI', False)
32
32
33 try:
33 try:
34 OP_NO_SSLv2 = ssl.OP_NO_SSLv2
34 OP_NO_SSLv2 = ssl.OP_NO_SSLv2
35 OP_NO_SSLv3 = ssl.OP_NO_SSLv3
35 OP_NO_SSLv3 = ssl.OP_NO_SSLv3
36 except AttributeError:
36 except AttributeError:
37 OP_NO_SSLv2 = 0x1000000
37 OP_NO_SSLv2 = 0x1000000
38 OP_NO_SSLv3 = 0x2000000
38 OP_NO_SSLv3 = 0x2000000
39
39
40 try:
40 try:
41 # ssl.SSLContext was added in 2.7.9 and presence indicates modern
41 # ssl.SSLContext was added in 2.7.9 and presence indicates modern
42 # SSL/TLS features are available.
42 # SSL/TLS features are available.
43 SSLContext = ssl.SSLContext
43 SSLContext = ssl.SSLContext
44 modernssl = True
44 modernssl = True
45 _canloaddefaultcerts = util.safehasattr(SSLContext, 'load_default_certs')
45 _canloaddefaultcerts = util.safehasattr(SSLContext, 'load_default_certs')
46 except AttributeError:
46 except AttributeError:
47 modernssl = False
47 modernssl = False
48 _canloaddefaultcerts = False
48 _canloaddefaultcerts = False
49
49
50 # We implement SSLContext using the interface from the standard library.
50 # We implement SSLContext using the interface from the standard library.
51 class SSLContext(object):
51 class SSLContext(object):
52 # ssl.wrap_socket gained the "ciphers" named argument in 2.7.
52 # ssl.wrap_socket gained the "ciphers" named argument in 2.7.
53 _supportsciphers = sys.version_info >= (2, 7)
53 _supportsciphers = sys.version_info >= (2, 7)
54
54
55 def __init__(self, protocol):
55 def __init__(self, protocol):
56 # From the public interface of SSLContext
56 # From the public interface of SSLContext
57 self.protocol = protocol
57 self.protocol = protocol
58 self.check_hostname = False
58 self.check_hostname = False
59 self.options = 0
59 self.options = 0
60 self.verify_mode = ssl.CERT_NONE
60 self.verify_mode = ssl.CERT_NONE
61
61
62 # Used by our implementation.
62 # Used by our implementation.
63 self._certfile = None
63 self._certfile = None
64 self._keyfile = None
64 self._keyfile = None
65 self._certpassword = None
65 self._certpassword = None
66 self._cacerts = None
66 self._cacerts = None
67 self._ciphers = None
67 self._ciphers = None
68
68
69 def load_cert_chain(self, certfile, keyfile=None, password=None):
69 def load_cert_chain(self, certfile, keyfile=None, password=None):
70 self._certfile = certfile
70 self._certfile = certfile
71 self._keyfile = keyfile
71 self._keyfile = keyfile
72 self._certpassword = password
72 self._certpassword = password
73
73
74 def load_default_certs(self, purpose=None):
74 def load_default_certs(self, purpose=None):
75 pass
75 pass
76
76
77 def load_verify_locations(self, cafile=None, capath=None, cadata=None):
77 def load_verify_locations(self, cafile=None, capath=None, cadata=None):
78 if capath:
78 if capath:
79 raise error.Abort('capath not supported')
79 raise error.Abort('capath not supported')
80 if cadata:
80 if cadata:
81 raise error.Abort('cadata not supported')
81 raise error.Abort('cadata not supported')
82
82
83 self._cacerts = cafile
83 self._cacerts = cafile
84
84
85 def set_ciphers(self, ciphers):
85 def set_ciphers(self, ciphers):
86 if not self._supportsciphers:
86 if not self._supportsciphers:
87 raise error.Abort('setting ciphers not supported')
87 raise error.Abort('setting ciphers not supported')
88
88
89 self._ciphers = ciphers
89 self._ciphers = ciphers
90
90
91 def wrap_socket(self, socket, server_hostname=None, server_side=False):
91 def wrap_socket(self, socket, server_hostname=None, server_side=False):
92 # server_hostname is unique to SSLContext.wrap_socket and is used
92 # server_hostname is unique to SSLContext.wrap_socket and is used
93 # for SNI in that context. So there's nothing for us to do with it
93 # for SNI in that context. So there's nothing for us to do with it
94 # in this legacy code since we don't support SNI.
94 # in this legacy code since we don't support SNI.
95
95
96 args = {
96 args = {
97 'keyfile': self._keyfile,
97 'keyfile': self._keyfile,
98 'certfile': self._certfile,
98 'certfile': self._certfile,
99 'server_side': server_side,
99 'server_side': server_side,
100 'cert_reqs': self.verify_mode,
100 'cert_reqs': self.verify_mode,
101 'ssl_version': self.protocol,
101 'ssl_version': self.protocol,
102 'ca_certs': self._cacerts,
102 'ca_certs': self._cacerts,
103 }
103 }
104
104
105 if self._supportsciphers:
105 if self._supportsciphers:
106 args['ciphers'] = self._ciphers
106 args['ciphers'] = self._ciphers
107
107
108 return ssl.wrap_socket(socket, **args)
108 return ssl.wrap_socket(socket, **args)
109
109
110 def wrapsocket(sock, keyfile, certfile, ui, cert_reqs=ssl.CERT_NONE,
110 def wrapsocket(sock, keyfile, certfile, ui, cert_reqs=ssl.CERT_NONE,
111 ca_certs=None, serverhostname=None):
111 ca_certs=None, serverhostname=None):
112 """Add SSL/TLS to a socket.
112 """Add SSL/TLS to a socket.
113
113
114 This is a glorified wrapper for ``ssl.wrap_socket()``. It makes sane
114 This is a glorified wrapper for ``ssl.wrap_socket()``. It makes sane
115 choices based on what security options are available.
115 choices based on what security options are available.
116
116
117 In addition to the arguments supported by ``ssl.wrap_socket``, we allow
117 In addition to the arguments supported by ``ssl.wrap_socket``, we allow
118 the following additional arguments:
118 the following additional arguments:
119
119
120 * serverhostname - The expected hostname of the remote server. If the
120 * serverhostname - The expected hostname of the remote server. If the
121 server (and client) support SNI, this tells the server which certificate
121 server (and client) support SNI, this tells the server which certificate
122 to use.
122 to use.
123 """
123 """
124 # Despite its name, PROTOCOL_SSLv23 selects the highest protocol
124 # Despite its name, PROTOCOL_SSLv23 selects the highest protocol
125 # that both ends support, including TLS protocols. On legacy stacks,
125 # that both ends support, including TLS protocols. On legacy stacks,
126 # the highest it likely goes in TLS 1.0. On modern stacks, it can
126 # the highest it likely goes in TLS 1.0. On modern stacks, it can
127 # support TLS 1.2.
127 # support TLS 1.2.
128 #
128 #
129 # The PROTOCOL_TLSv* constants select a specific TLS version
129 # The PROTOCOL_TLSv* constants select a specific TLS version
130 # only (as opposed to multiple versions). So the method for
130 # only (as opposed to multiple versions). So the method for
131 # supporting multiple TLS versions is to use PROTOCOL_SSLv23 and
131 # supporting multiple TLS versions is to use PROTOCOL_SSLv23 and
132 # disable protocols via SSLContext.options and OP_NO_* constants.
132 # disable protocols via SSLContext.options and OP_NO_* constants.
133 # However, SSLContext.options doesn't work unless we have the
133 # However, SSLContext.options doesn't work unless we have the
134 # full/real SSLContext available to us.
134 # full/real SSLContext available to us.
135 #
135 #
136 # SSLv2 and SSLv3 are broken. We ban them outright.
136 # SSLv2 and SSLv3 are broken. We ban them outright.
137 if modernssl:
137 if modernssl:
138 protocol = ssl.PROTOCOL_SSLv23
138 protocol = ssl.PROTOCOL_SSLv23
139 else:
139 else:
140 protocol = ssl.PROTOCOL_TLSv1
140 protocol = ssl.PROTOCOL_TLSv1
141
141
142 # TODO use ssl.create_default_context() on modernssl.
142 # TODO use ssl.create_default_context() on modernssl.
143 sslcontext = SSLContext(protocol)
143 sslcontext = SSLContext(protocol)
144
144
145 # This is a no-op on old Python.
145 # This is a no-op on old Python.
146 sslcontext.options |= OP_NO_SSLv2 | OP_NO_SSLv3
146 sslcontext.options |= OP_NO_SSLv2 | OP_NO_SSLv3
147
147
148 # This still works on our fake SSLContext.
148 # This still works on our fake SSLContext.
149 sslcontext.verify_mode = cert_reqs
149 sslcontext.verify_mode = cert_reqs
150
150
151 if certfile is not None:
151 if certfile is not None:
152 def password():
152 def password():
153 f = keyfile or certfile
153 f = keyfile or certfile
154 return ui.getpass(_('passphrase for %s: ') % f, '')
154 return ui.getpass(_('passphrase for %s: ') % f, '')
155 sslcontext.load_cert_chain(certfile, keyfile, password)
155 sslcontext.load_cert_chain(certfile, keyfile, password)
156
156
157 if ca_certs is not None:
157 if ca_certs is not None:
158 sslcontext.load_verify_locations(cafile=ca_certs)
158 sslcontext.load_verify_locations(cafile=ca_certs)
159 else:
159 else:
160 # This is a no-op on old Python.
160 # This is a no-op on old Python.
161 sslcontext.load_default_certs()
161 sslcontext.load_default_certs()
162
162
163 sslsocket = sslcontext.wrap_socket(sock, server_hostname=serverhostname)
163 sslsocket = sslcontext.wrap_socket(sock, server_hostname=serverhostname)
164 # check if wrap_socket failed silently because socket had been
164 # check if wrap_socket failed silently because socket had been
165 # closed
165 # closed
166 # - see http://bugs.python.org/issue13721
166 # - see http://bugs.python.org/issue13721
167 if not sslsocket.cipher():
167 if not sslsocket.cipher():
168 raise error.Abort(_('ssl connection failed'))
168 raise error.Abort(_('ssl connection failed'))
169 return sslsocket
169 return sslsocket
170
170
171 class wildcarderror(Exception):
171 class wildcarderror(Exception):
172 """Represents an error parsing wildcards in DNS name."""
172 """Represents an error parsing wildcards in DNS name."""
173
173
174 def _dnsnamematch(dn, hostname, maxwildcards=1):
174 def _dnsnamematch(dn, hostname, maxwildcards=1):
175 """Match DNS names according RFC 6125 section 6.4.3.
175 """Match DNS names according RFC 6125 section 6.4.3.
176
176
177 This code is effectively copied from CPython's ssl._dnsname_match.
177 This code is effectively copied from CPython's ssl._dnsname_match.
178
178
179 Returns a bool indicating whether the expected hostname matches
179 Returns a bool indicating whether the expected hostname matches
180 the value in ``dn``.
180 the value in ``dn``.
181 """
181 """
182 pats = []
182 pats = []
183 if not dn:
183 if not dn:
184 return False
184 return False
185
185
186 pieces = dn.split(r'.')
186 pieces = dn.split(r'.')
187 leftmost = pieces[0]
187 leftmost = pieces[0]
188 remainder = pieces[1:]
188 remainder = pieces[1:]
189 wildcards = leftmost.count('*')
189 wildcards = leftmost.count('*')
190 if wildcards > maxwildcards:
190 if wildcards > maxwildcards:
191 raise wildcarderror(
191 raise wildcarderror(
192 _('too many wildcards in certificate DNS name: %s') % dn)
192 _('too many wildcards in certificate DNS name: %s') % dn)
193
193
194 # speed up common case w/o wildcards
194 # speed up common case w/o wildcards
195 if not wildcards:
195 if not wildcards:
196 return dn.lower() == hostname.lower()
196 return dn.lower() == hostname.lower()
197
197
198 # RFC 6125, section 6.4.3, subitem 1.
198 # RFC 6125, section 6.4.3, subitem 1.
199 # The client SHOULD NOT attempt to match a presented identifier in which
199 # The client SHOULD NOT attempt to match a presented identifier in which
200 # the wildcard character comprises a label other than the left-most label.
200 # the wildcard character comprises a label other than the left-most label.
201 if leftmost == '*':
201 if leftmost == '*':
202 # When '*' is a fragment by itself, it matches a non-empty dotless
202 # When '*' is a fragment by itself, it matches a non-empty dotless
203 # fragment.
203 # fragment.
204 pats.append('[^.]+')
204 pats.append('[^.]+')
205 elif leftmost.startswith('xn--') or hostname.startswith('xn--'):
205 elif leftmost.startswith('xn--') or hostname.startswith('xn--'):
206 # RFC 6125, section 6.4.3, subitem 3.
206 # RFC 6125, section 6.4.3, subitem 3.
207 # The client SHOULD NOT attempt to match a presented identifier
207 # The client SHOULD NOT attempt to match a presented identifier
208 # where the wildcard character is embedded within an A-label or
208 # where the wildcard character is embedded within an A-label or
209 # U-label of an internationalized domain name.
209 # U-label of an internationalized domain name.
210 pats.append(re.escape(leftmost))
210 pats.append(re.escape(leftmost))
211 else:
211 else:
212 # Otherwise, '*' matches any dotless string, e.g. www*
212 # Otherwise, '*' matches any dotless string, e.g. www*
213 pats.append(re.escape(leftmost).replace(r'\*', '[^.]*'))
213 pats.append(re.escape(leftmost).replace(r'\*', '[^.]*'))
214
214
215 # add the remaining fragments, ignore any wildcards
215 # add the remaining fragments, ignore any wildcards
216 for frag in remainder:
216 for frag in remainder:
217 pats.append(re.escape(frag))
217 pats.append(re.escape(frag))
218
218
219 pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)
219 pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)
220 return pat.match(hostname) is not None
220 return pat.match(hostname) is not None
221
221
222 def _verifycert(cert, hostname):
222 def _verifycert(cert, hostname):
223 '''Verify that cert (in socket.getpeercert() format) matches hostname.
223 '''Verify that cert (in socket.getpeercert() format) matches hostname.
224 CRLs is not handled.
224 CRLs is not handled.
225
225
226 Returns error message if any problems are found and None on success.
226 Returns error message if any problems are found and None on success.
227 '''
227 '''
228 if not cert:
228 if not cert:
229 return _('no certificate received')
229 return _('no certificate received')
230
230
231 dnsnames = []
231 dnsnames = []
232 san = cert.get('subjectAltName', [])
232 san = cert.get('subjectAltName', [])
233 for key, value in san:
233 for key, value in san:
234 if key == 'DNS':
234 if key == 'DNS':
235 try:
235 try:
236 if _dnsnamematch(value, hostname):
236 if _dnsnamematch(value, hostname):
237 return
237 return
238 except wildcarderror as e:
238 except wildcarderror as e:
239 return e.message
239 return e.args[0]
240
240
241 dnsnames.append(value)
241 dnsnames.append(value)
242
242
243 if not dnsnames:
243 if not dnsnames:
244 # The subject is only checked when there is no DNS in subjectAltName.
244 # The subject is only checked when there is no DNS in subjectAltName.
245 for sub in cert.get('subject', []):
245 for sub in cert.get('subject', []):
246 for key, value in sub:
246 for key, value in sub:
247 # According to RFC 2818 the most specific Common Name must
247 # According to RFC 2818 the most specific Common Name must
248 # be used.
248 # be used.
249 if key == 'commonName':
249 if key == 'commonName':
250 # 'subject' entries are unicide.
250 # 'subject' entries are unicide.
251 try:
251 try:
252 value = value.encode('ascii')
252 value = value.encode('ascii')
253 except UnicodeEncodeError:
253 except UnicodeEncodeError:
254 return _('IDN in certificate not supported')
254 return _('IDN in certificate not supported')
255
255
256 try:
256 try:
257 if _dnsnamematch(value, hostname):
257 if _dnsnamematch(value, hostname):
258 return
258 return
259 except wildcarderror as e:
259 except wildcarderror as e:
260 return e.message
260 return e.args[0]
261
261
262 dnsnames.append(value)
262 dnsnames.append(value)
263
263
264 if len(dnsnames) > 1:
264 if len(dnsnames) > 1:
265 return _('certificate is for %s') % ', '.join(dnsnames)
265 return _('certificate is for %s') % ', '.join(dnsnames)
266 elif len(dnsnames) == 1:
266 elif len(dnsnames) == 1:
267 return _('certificate is for %s') % dnsnames[0]
267 return _('certificate is for %s') % dnsnames[0]
268 else:
268 else:
269 return _('no commonName or subjectAltName found in certificate')
269 return _('no commonName or subjectAltName found in certificate')
270
270
271
271
272 # CERT_REQUIRED means fetch the cert from the server all the time AND
272 # CERT_REQUIRED means fetch the cert from the server all the time AND
273 # validate it against the CA store provided in web.cacerts.
273 # validate it against the CA store provided in web.cacerts.
274
274
275 def _plainapplepython():
275 def _plainapplepython():
276 """return true if this seems to be a pure Apple Python that
276 """return true if this seems to be a pure Apple Python that
277 * is unfrozen and presumably has the whole mercurial module in the file
277 * is unfrozen and presumably has the whole mercurial module in the file
278 system
278 system
279 * presumably is an Apple Python that uses Apple OpenSSL which has patches
279 * presumably is an Apple Python that uses Apple OpenSSL which has patches
280 for using system certificate store CAs in addition to the provided
280 for using system certificate store CAs in addition to the provided
281 cacerts file
281 cacerts file
282 """
282 """
283 if sys.platform != 'darwin' or util.mainfrozen() or not sys.executable:
283 if sys.platform != 'darwin' or util.mainfrozen() or not sys.executable:
284 return False
284 return False
285 exe = os.path.realpath(sys.executable).lower()
285 exe = os.path.realpath(sys.executable).lower()
286 return (exe.startswith('/usr/bin/python') or
286 return (exe.startswith('/usr/bin/python') or
287 exe.startswith('/system/library/frameworks/python.framework/'))
287 exe.startswith('/system/library/frameworks/python.framework/'))
288
288
289 def _defaultcacerts():
289 def _defaultcacerts():
290 """return path to CA certificates; None for system's store; ! to disable"""
290 """return path to CA certificates; None for system's store; ! to disable"""
291 if _plainapplepython():
291 if _plainapplepython():
292 dummycert = os.path.join(os.path.dirname(__file__), 'dummycert.pem')
292 dummycert = os.path.join(os.path.dirname(__file__), 'dummycert.pem')
293 if os.path.exists(dummycert):
293 if os.path.exists(dummycert):
294 return dummycert
294 return dummycert
295 if _canloaddefaultcerts:
295 if _canloaddefaultcerts:
296 return None
296 return None
297 return '!'
297 return '!'
298
298
299 def sslkwargs(ui, host):
299 def sslkwargs(ui, host):
300 kws = {'ui': ui}
300 kws = {'ui': ui}
301 hostfingerprint = ui.config('hostfingerprints', host)
301 hostfingerprint = ui.config('hostfingerprints', host)
302 if hostfingerprint:
302 if hostfingerprint:
303 return kws
303 return kws
304 cacerts = ui.config('web', 'cacerts')
304 cacerts = ui.config('web', 'cacerts')
305 if cacerts == '!':
305 if cacerts == '!':
306 pass
306 pass
307 elif cacerts:
307 elif cacerts:
308 cacerts = util.expandpath(cacerts)
308 cacerts = util.expandpath(cacerts)
309 if not os.path.exists(cacerts):
309 if not os.path.exists(cacerts):
310 raise error.Abort(_('could not find web.cacerts: %s') % cacerts)
310 raise error.Abort(_('could not find web.cacerts: %s') % cacerts)
311 else:
311 else:
312 cacerts = _defaultcacerts()
312 cacerts = _defaultcacerts()
313 if cacerts and cacerts != '!':
313 if cacerts and cacerts != '!':
314 ui.debug('using %s to enable OS X system CA\n' % cacerts)
314 ui.debug('using %s to enable OS X system CA\n' % cacerts)
315 ui.setconfig('web', 'cacerts', cacerts, 'defaultcacerts')
315 ui.setconfig('web', 'cacerts', cacerts, 'defaultcacerts')
316 if cacerts != '!':
316 if cacerts != '!':
317 kws.update({'ca_certs': cacerts,
317 kws.update({'ca_certs': cacerts,
318 'cert_reqs': ssl.CERT_REQUIRED,
318 'cert_reqs': ssl.CERT_REQUIRED,
319 })
319 })
320 return kws
320 return kws
321
321
322 class validator(object):
322 class validator(object):
323 def __init__(self, ui, host):
323 def __init__(self, ui, host):
324 self.ui = ui
324 self.ui = ui
325 self.host = host
325 self.host = host
326
326
327 def __call__(self, sock, strict=False):
327 def __call__(self, sock, strict=False):
328 host = self.host
328 host = self.host
329
329
330 if not sock.cipher(): # work around http://bugs.python.org/issue13721
330 if not sock.cipher(): # work around http://bugs.python.org/issue13721
331 raise error.Abort(_('%s ssl connection error') % host)
331 raise error.Abort(_('%s ssl connection error') % host)
332 try:
332 try:
333 peercert = sock.getpeercert(True)
333 peercert = sock.getpeercert(True)
334 peercert2 = sock.getpeercert()
334 peercert2 = sock.getpeercert()
335 except AttributeError:
335 except AttributeError:
336 raise error.Abort(_('%s ssl connection error') % host)
336 raise error.Abort(_('%s ssl connection error') % host)
337
337
338 if not peercert:
338 if not peercert:
339 raise error.Abort(_('%s certificate error: '
339 raise error.Abort(_('%s certificate error: '
340 'no certificate received') % host)
340 'no certificate received') % host)
341
341
342 # If a certificate fingerprint is pinned, use it and only it to
342 # If a certificate fingerprint is pinned, use it and only it to
343 # validate the remote cert.
343 # validate the remote cert.
344 hostfingerprints = self.ui.configlist('hostfingerprints', host)
344 hostfingerprints = self.ui.configlist('hostfingerprints', host)
345 peerfingerprint = util.sha1(peercert).hexdigest()
345 peerfingerprint = util.sha1(peercert).hexdigest()
346 nicefingerprint = ":".join([peerfingerprint[x:x + 2]
346 nicefingerprint = ":".join([peerfingerprint[x:x + 2]
347 for x in xrange(0, len(peerfingerprint), 2)])
347 for x in xrange(0, len(peerfingerprint), 2)])
348 if hostfingerprints:
348 if hostfingerprints:
349 fingerprintmatch = False
349 fingerprintmatch = False
350 for hostfingerprint in hostfingerprints:
350 for hostfingerprint in hostfingerprints:
351 if peerfingerprint.lower() == \
351 if peerfingerprint.lower() == \
352 hostfingerprint.replace(':', '').lower():
352 hostfingerprint.replace(':', '').lower():
353 fingerprintmatch = True
353 fingerprintmatch = True
354 break
354 break
355 if not fingerprintmatch:
355 if not fingerprintmatch:
356 raise error.Abort(_('certificate for %s has unexpected '
356 raise error.Abort(_('certificate for %s has unexpected '
357 'fingerprint %s') % (host, nicefingerprint),
357 'fingerprint %s') % (host, nicefingerprint),
358 hint=_('check hostfingerprint configuration'))
358 hint=_('check hostfingerprint configuration'))
359 self.ui.debug('%s certificate matched fingerprint %s\n' %
359 self.ui.debug('%s certificate matched fingerprint %s\n' %
360 (host, nicefingerprint))
360 (host, nicefingerprint))
361 return
361 return
362
362
363 # No pinned fingerprint. Establish trust by looking at the CAs.
363 # No pinned fingerprint. Establish trust by looking at the CAs.
364 cacerts = self.ui.config('web', 'cacerts')
364 cacerts = self.ui.config('web', 'cacerts')
365 if cacerts != '!':
365 if cacerts != '!':
366 msg = _verifycert(peercert2, host)
366 msg = _verifycert(peercert2, host)
367 if msg:
367 if msg:
368 raise error.Abort(_('%s certificate error: %s') % (host, msg),
368 raise error.Abort(_('%s certificate error: %s') % (host, msg),
369 hint=_('configure hostfingerprint %s or use '
369 hint=_('configure hostfingerprint %s or use '
370 '--insecure to connect insecurely') %
370 '--insecure to connect insecurely') %
371 nicefingerprint)
371 nicefingerprint)
372 self.ui.debug('%s certificate successfully verified\n' % host)
372 self.ui.debug('%s certificate successfully verified\n' % host)
373 elif strict:
373 elif strict:
374 raise error.Abort(_('%s certificate with fingerprint %s not '
374 raise error.Abort(_('%s certificate with fingerprint %s not '
375 'verified') % (host, nicefingerprint),
375 'verified') % (host, nicefingerprint),
376 hint=_('check hostfingerprints or web.cacerts '
376 hint=_('check hostfingerprints or web.cacerts '
377 'config setting'))
377 'config setting'))
378 else:
378 else:
379 self.ui.warn(_('warning: %s certificate with fingerprint %s not '
379 self.ui.warn(_('warning: %s certificate with fingerprint %s not '
380 'verified (check hostfingerprints or web.cacerts '
380 'verified (check hostfingerprints or web.cacerts '
381 'config setting)\n') %
381 'config setting)\n') %
382 (host, nicefingerprint))
382 (host, nicefingerprint))
General Comments 0
You need to be logged in to leave comments. Login now