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