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