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