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