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