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