##// END OF EJS Templates
ssl: resolve symlink before checking for Apple python executable (issue4588)...
Yuya Nishihara -
r24614:241d98d8 default
parent child Browse files
Show More
@@ -1,231 +1,231 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 import os, sys
9 import os, sys
10
10
11 from mercurial import util
11 from mercurial import util
12 from mercurial.i18n import _
12 from mercurial.i18n import _
13
13
14 _canloaddefaultcerts = False
14 _canloaddefaultcerts = False
15 try:
15 try:
16 # avoid using deprecated/broken FakeSocket in python 2.6
16 # avoid using deprecated/broken FakeSocket in python 2.6
17 import ssl
17 import ssl
18 CERT_REQUIRED = ssl.CERT_REQUIRED
18 CERT_REQUIRED = ssl.CERT_REQUIRED
19 try:
19 try:
20 ssl_context = ssl.SSLContext
20 ssl_context = ssl.SSLContext
21 _canloaddefaultcerts = util.safehasattr(ssl_context,
21 _canloaddefaultcerts = util.safehasattr(ssl_context,
22 'load_default_certs')
22 'load_default_certs')
23
23
24 def ssl_wrap_socket(sock, keyfile, certfile, cert_reqs=ssl.CERT_NONE,
24 def ssl_wrap_socket(sock, keyfile, certfile, cert_reqs=ssl.CERT_NONE,
25 ca_certs=None, serverhostname=None):
25 ca_certs=None, serverhostname=None):
26 # Allow any version of SSL starting with TLSv1 and
26 # Allow any version of SSL starting with TLSv1 and
27 # up. Note that specifying TLSv1 here prohibits use of
27 # up. Note that specifying TLSv1 here prohibits use of
28 # newer standards (like TLSv1_2), so this is the right way
28 # newer standards (like TLSv1_2), so this is the right way
29 # to do this. Note that in the future it'd be better to
29 # to do this. Note that in the future it'd be better to
30 # support using ssl.create_default_context(), which sets
30 # support using ssl.create_default_context(), which sets
31 # up a bunch of things in smart ways (strong ciphers,
31 # up a bunch of things in smart ways (strong ciphers,
32 # protocol versions, etc) and is upgraded by Python
32 # protocol versions, etc) and is upgraded by Python
33 # maintainers for us, but that breaks too many things to
33 # maintainers for us, but that breaks too many things to
34 # do it in a hurry.
34 # do it in a hurry.
35 sslcontext = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
35 sslcontext = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
36 sslcontext.options &= ssl.OP_NO_SSLv2 & ssl.OP_NO_SSLv3
36 sslcontext.options &= ssl.OP_NO_SSLv2 & ssl.OP_NO_SSLv3
37 if certfile is not None:
37 if certfile is not None:
38 sslcontext.load_cert_chain(certfile, keyfile)
38 sslcontext.load_cert_chain(certfile, keyfile)
39 sslcontext.verify_mode = cert_reqs
39 sslcontext.verify_mode = cert_reqs
40 if ca_certs is not None:
40 if ca_certs is not None:
41 sslcontext.load_verify_locations(cafile=ca_certs)
41 sslcontext.load_verify_locations(cafile=ca_certs)
42 elif _canloaddefaultcerts:
42 elif _canloaddefaultcerts:
43 sslcontext.load_default_certs()
43 sslcontext.load_default_certs()
44
44
45 sslsocket = sslcontext.wrap_socket(sock,
45 sslsocket = sslcontext.wrap_socket(sock,
46 server_hostname=serverhostname)
46 server_hostname=serverhostname)
47 # check if wrap_socket failed silently because socket had been
47 # check if wrap_socket failed silently because socket had been
48 # closed
48 # closed
49 # - see http://bugs.python.org/issue13721
49 # - see http://bugs.python.org/issue13721
50 if not sslsocket.cipher():
50 if not sslsocket.cipher():
51 raise util.Abort(_('ssl connection failed'))
51 raise util.Abort(_('ssl connection failed'))
52 return sslsocket
52 return sslsocket
53 except AttributeError:
53 except AttributeError:
54 def ssl_wrap_socket(sock, keyfile, certfile, cert_reqs=ssl.CERT_NONE,
54 def ssl_wrap_socket(sock, keyfile, certfile, cert_reqs=ssl.CERT_NONE,
55 ca_certs=None, serverhostname=None):
55 ca_certs=None, serverhostname=None):
56 sslsocket = ssl.wrap_socket(sock, keyfile, certfile,
56 sslsocket = ssl.wrap_socket(sock, keyfile, certfile,
57 cert_reqs=cert_reqs, ca_certs=ca_certs,
57 cert_reqs=cert_reqs, ca_certs=ca_certs,
58 ssl_version=ssl.PROTOCOL_TLSv1)
58 ssl_version=ssl.PROTOCOL_TLSv1)
59 # check if wrap_socket failed silently because socket had been
59 # check if wrap_socket failed silently because socket had been
60 # closed
60 # closed
61 # - see http://bugs.python.org/issue13721
61 # - see http://bugs.python.org/issue13721
62 if not sslsocket.cipher():
62 if not sslsocket.cipher():
63 raise util.Abort(_('ssl connection failed'))
63 raise util.Abort(_('ssl connection failed'))
64 return sslsocket
64 return sslsocket
65 except ImportError:
65 except ImportError:
66 CERT_REQUIRED = 2
66 CERT_REQUIRED = 2
67
67
68 import socket, httplib
68 import socket, httplib
69
69
70 def ssl_wrap_socket(sock, keyfile, certfile, cert_reqs=CERT_REQUIRED,
70 def ssl_wrap_socket(sock, keyfile, certfile, cert_reqs=CERT_REQUIRED,
71 ca_certs=None, serverhostname=None):
71 ca_certs=None, serverhostname=None):
72 if not util.safehasattr(socket, 'ssl'):
72 if not util.safehasattr(socket, 'ssl'):
73 raise util.Abort(_('Python SSL support not found'))
73 raise util.Abort(_('Python SSL support not found'))
74 if ca_certs:
74 if ca_certs:
75 raise util.Abort(_(
75 raise util.Abort(_(
76 'certificate checking requires Python 2.6'))
76 'certificate checking requires Python 2.6'))
77
77
78 ssl = socket.ssl(sock, keyfile, certfile)
78 ssl = socket.ssl(sock, keyfile, certfile)
79 return httplib.FakeSocket(sock, ssl)
79 return httplib.FakeSocket(sock, ssl)
80
80
81 def _verifycert(cert, hostname):
81 def _verifycert(cert, hostname):
82 '''Verify that cert (in socket.getpeercert() format) matches hostname.
82 '''Verify that cert (in socket.getpeercert() format) matches hostname.
83 CRLs is not handled.
83 CRLs is not handled.
84
84
85 Returns error message if any problems are found and None on success.
85 Returns error message if any problems are found and None on success.
86 '''
86 '''
87 if not cert:
87 if not cert:
88 return _('no certificate received')
88 return _('no certificate received')
89 dnsname = hostname.lower()
89 dnsname = hostname.lower()
90 def matchdnsname(certname):
90 def matchdnsname(certname):
91 return (certname == dnsname or
91 return (certname == dnsname or
92 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1])
92 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1])
93
93
94 san = cert.get('subjectAltName', [])
94 san = cert.get('subjectAltName', [])
95 if san:
95 if san:
96 certnames = [value.lower() for key, value in san if key == 'DNS']
96 certnames = [value.lower() for key, value in san if key == 'DNS']
97 for name in certnames:
97 for name in certnames:
98 if matchdnsname(name):
98 if matchdnsname(name):
99 return None
99 return None
100 if certnames:
100 if certnames:
101 return _('certificate is for %s') % ', '.join(certnames)
101 return _('certificate is for %s') % ', '.join(certnames)
102
102
103 # subject is only checked when subjectAltName is empty
103 # subject is only checked when subjectAltName is empty
104 for s in cert.get('subject', []):
104 for s in cert.get('subject', []):
105 key, value = s[0]
105 key, value = s[0]
106 if key == 'commonName':
106 if key == 'commonName':
107 try:
107 try:
108 # 'subject' entries are unicode
108 # 'subject' entries are unicode
109 certname = value.lower().encode('ascii')
109 certname = value.lower().encode('ascii')
110 except UnicodeEncodeError:
110 except UnicodeEncodeError:
111 return _('IDN in certificate not supported')
111 return _('IDN in certificate not supported')
112 if matchdnsname(certname):
112 if matchdnsname(certname):
113 return None
113 return None
114 return _('certificate is for %s') % certname
114 return _('certificate is for %s') % certname
115 return _('no commonName or subjectAltName found in certificate')
115 return _('no commonName or subjectAltName found in certificate')
116
116
117
117
118 # CERT_REQUIRED means fetch the cert from the server all the time AND
118 # CERT_REQUIRED means fetch the cert from the server all the time AND
119 # validate it against the CA store provided in web.cacerts.
119 # validate it against the CA store provided in web.cacerts.
120 #
120 #
121 # We COMPLETELY ignore CERT_REQUIRED on Python <= 2.5, as it's totally
121 # We COMPLETELY ignore CERT_REQUIRED on Python <= 2.5, as it's totally
122 # busted on those versions.
122 # busted on those versions.
123
123
124 def _plainapplepython():
124 def _plainapplepython():
125 """return true if this seems to be a pure Apple Python that
125 """return true if this seems to be a pure Apple Python that
126 * is unfrozen and presumably has the whole mercurial module in the file
126 * is unfrozen and presumably has the whole mercurial module in the file
127 system
127 system
128 * presumably is an Apple Python that uses Apple OpenSSL which has patches
128 * presumably is an Apple Python that uses Apple OpenSSL which has patches
129 for using system certificate store CAs in addition to the provided
129 for using system certificate store CAs in addition to the provided
130 cacerts file
130 cacerts file
131 """
131 """
132 if sys.platform != 'darwin' or util.mainfrozen():
132 if sys.platform != 'darwin' or util.mainfrozen() or not sys.executable:
133 return False
133 return False
134 exe = (sys.executable or '').lower()
134 exe = os.path.realpath(sys.executable).lower()
135 return (exe.startswith('/usr/bin/python') or
135 return (exe.startswith('/usr/bin/python') or
136 exe.startswith('/system/library/frameworks/python.framework/'))
136 exe.startswith('/system/library/frameworks/python.framework/'))
137
137
138 def _defaultcacerts():
138 def _defaultcacerts():
139 """return path to CA certificates; None for system's store; ! to disable"""
139 """return path to CA certificates; None for system's store; ! to disable"""
140 if _plainapplepython():
140 if _plainapplepython():
141 dummycert = os.path.join(os.path.dirname(__file__), 'dummycert.pem')
141 dummycert = os.path.join(os.path.dirname(__file__), 'dummycert.pem')
142 if os.path.exists(dummycert):
142 if os.path.exists(dummycert):
143 return dummycert
143 return dummycert
144 if _canloaddefaultcerts:
144 if _canloaddefaultcerts:
145 return None
145 return None
146 return '!'
146 return '!'
147
147
148 def sslkwargs(ui, host):
148 def sslkwargs(ui, host):
149 kws = {}
149 kws = {}
150 hostfingerprint = ui.config('hostfingerprints', host)
150 hostfingerprint = ui.config('hostfingerprints', host)
151 if hostfingerprint:
151 if hostfingerprint:
152 return kws
152 return kws
153 cacerts = ui.config('web', 'cacerts')
153 cacerts = ui.config('web', 'cacerts')
154 if cacerts == '!':
154 if cacerts == '!':
155 pass
155 pass
156 elif cacerts:
156 elif cacerts:
157 cacerts = util.expandpath(cacerts)
157 cacerts = util.expandpath(cacerts)
158 if not os.path.exists(cacerts):
158 if not os.path.exists(cacerts):
159 raise util.Abort(_('could not find web.cacerts: %s') % cacerts)
159 raise util.Abort(_('could not find web.cacerts: %s') % cacerts)
160 else:
160 else:
161 cacerts = _defaultcacerts()
161 cacerts = _defaultcacerts()
162 if cacerts and cacerts != '!':
162 if cacerts and cacerts != '!':
163 ui.debug('using %s to enable OS X system CA\n' % cacerts)
163 ui.debug('using %s to enable OS X system CA\n' % cacerts)
164 ui.setconfig('web', 'cacerts', cacerts, 'defaultcacerts')
164 ui.setconfig('web', 'cacerts', cacerts, 'defaultcacerts')
165 if cacerts != '!':
165 if cacerts != '!':
166 kws.update({'ca_certs': cacerts,
166 kws.update({'ca_certs': cacerts,
167 'cert_reqs': CERT_REQUIRED,
167 'cert_reqs': CERT_REQUIRED,
168 })
168 })
169 return kws
169 return kws
170
170
171 class validator(object):
171 class validator(object):
172 def __init__(self, ui, host):
172 def __init__(self, ui, host):
173 self.ui = ui
173 self.ui = ui
174 self.host = host
174 self.host = host
175
175
176 def __call__(self, sock, strict=False):
176 def __call__(self, sock, strict=False):
177 host = self.host
177 host = self.host
178 cacerts = self.ui.config('web', 'cacerts')
178 cacerts = self.ui.config('web', 'cacerts')
179 hostfingerprint = self.ui.config('hostfingerprints', host)
179 hostfingerprint = self.ui.config('hostfingerprints', host)
180 if not getattr(sock, 'getpeercert', False): # python 2.5 ?
180 if not getattr(sock, 'getpeercert', False): # python 2.5 ?
181 if hostfingerprint:
181 if hostfingerprint:
182 raise util.Abort(_("host fingerprint for %s can't be "
182 raise util.Abort(_("host fingerprint for %s can't be "
183 "verified (Python too old)") % host)
183 "verified (Python too old)") % host)
184 if strict:
184 if strict:
185 raise util.Abort(_("certificate for %s can't be verified "
185 raise util.Abort(_("certificate for %s can't be verified "
186 "(Python too old)") % host)
186 "(Python too old)") % host)
187 if self.ui.configbool('ui', 'reportoldssl', True):
187 if self.ui.configbool('ui', 'reportoldssl', True):
188 self.ui.warn(_("warning: certificate for %s can't be verified "
188 self.ui.warn(_("warning: certificate for %s can't be verified "
189 "(Python too old)\n") % host)
189 "(Python too old)\n") % host)
190 return
190 return
191
191
192 if not sock.cipher(): # work around http://bugs.python.org/issue13721
192 if not sock.cipher(): # work around http://bugs.python.org/issue13721
193 raise util.Abort(_('%s ssl connection error') % host)
193 raise util.Abort(_('%s ssl connection error') % host)
194 try:
194 try:
195 peercert = sock.getpeercert(True)
195 peercert = sock.getpeercert(True)
196 peercert2 = sock.getpeercert()
196 peercert2 = sock.getpeercert()
197 except AttributeError:
197 except AttributeError:
198 raise util.Abort(_('%s ssl connection error') % host)
198 raise util.Abort(_('%s ssl connection error') % host)
199
199
200 if not peercert:
200 if not peercert:
201 raise util.Abort(_('%s certificate error: '
201 raise util.Abort(_('%s certificate error: '
202 'no certificate received') % host)
202 'no certificate received') % host)
203 peerfingerprint = util.sha1(peercert).hexdigest()
203 peerfingerprint = util.sha1(peercert).hexdigest()
204 nicefingerprint = ":".join([peerfingerprint[x:x + 2]
204 nicefingerprint = ":".join([peerfingerprint[x:x + 2]
205 for x in xrange(0, len(peerfingerprint), 2)])
205 for x in xrange(0, len(peerfingerprint), 2)])
206 if hostfingerprint:
206 if hostfingerprint:
207 if peerfingerprint.lower() != \
207 if peerfingerprint.lower() != \
208 hostfingerprint.replace(':', '').lower():
208 hostfingerprint.replace(':', '').lower():
209 raise util.Abort(_('certificate for %s has unexpected '
209 raise util.Abort(_('certificate for %s has unexpected '
210 'fingerprint %s') % (host, nicefingerprint),
210 'fingerprint %s') % (host, nicefingerprint),
211 hint=_('check hostfingerprint configuration'))
211 hint=_('check hostfingerprint configuration'))
212 self.ui.debug('%s certificate matched fingerprint %s\n' %
212 self.ui.debug('%s certificate matched fingerprint %s\n' %
213 (host, nicefingerprint))
213 (host, nicefingerprint))
214 elif cacerts != '!':
214 elif cacerts != '!':
215 msg = _verifycert(peercert2, host)
215 msg = _verifycert(peercert2, host)
216 if msg:
216 if msg:
217 raise util.Abort(_('%s certificate error: %s') % (host, msg),
217 raise util.Abort(_('%s certificate error: %s') % (host, msg),
218 hint=_('configure hostfingerprint %s or use '
218 hint=_('configure hostfingerprint %s or use '
219 '--insecure to connect insecurely') %
219 '--insecure to connect insecurely') %
220 nicefingerprint)
220 nicefingerprint)
221 self.ui.debug('%s certificate successfully verified\n' % host)
221 self.ui.debug('%s certificate successfully verified\n' % host)
222 elif strict:
222 elif strict:
223 raise util.Abort(_('%s certificate with fingerprint %s not '
223 raise util.Abort(_('%s certificate with fingerprint %s not '
224 'verified') % (host, nicefingerprint),
224 'verified') % (host, nicefingerprint),
225 hint=_('check hostfingerprints or web.cacerts '
225 hint=_('check hostfingerprints or web.cacerts '
226 'config setting'))
226 'config setting'))
227 else:
227 else:
228 self.ui.warn(_('warning: %s certificate with fingerprint %s not '
228 self.ui.warn(_('warning: %s certificate with fingerprint %s not '
229 'verified (check hostfingerprints or web.cacerts '
229 'verified (check hostfingerprints or web.cacerts '
230 'config setting)\n') %
230 'config setting)\n') %
231 (host, nicefingerprint))
231 (host, nicefingerprint))
General Comments 0
You need to be logged in to leave comments. Login now