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