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