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