comparison mercurial/sslutil.py @ 29113:5b9577edf745

sslutil: use CA loaded state to drive validation logic Until now, sslkwargs may set web.cacerts=! to indicate that system certs could not be found. This is really obtuse because sslkwargs effectively sets state on a global object which bypasses wrapsocket() and is later consulted by validator.__call__. This is madness. This patch introduces an attribute on the wrapped socket instance indicating whether system CAs were loaded. We can set this directly inside wrapsocket() because that function knows everything that sslkwargs() does - and more. With this attribute set on the socket, we refactor validator.__call__ to use it. Since we no longer have a need for setting web.cacerts=! in sslkwargs, we remove that. I think the new logic is much easier to understand and will enable behavior to be changed more easily.
author Gregory Szorc <gregory.szorc@gmail.com>
date Thu, 05 May 2016 00:38:18 -0700
parents 5edc5acecc83
children ef316c653b7f
comparison
equal deleted inserted replaced
29112:5edc5acecc83 29113:5b9577edf745
153 return ui.getpass(_('passphrase for %s: ') % f, '') 153 return ui.getpass(_('passphrase for %s: ') % f, '')
154 sslcontext.load_cert_chain(certfile, keyfile, password) 154 sslcontext.load_cert_chain(certfile, keyfile, password)
155 155
156 if ca_certs is not None: 156 if ca_certs is not None:
157 sslcontext.load_verify_locations(cafile=ca_certs) 157 sslcontext.load_verify_locations(cafile=ca_certs)
158 caloaded = True
158 else: 159 else:
159 # This is a no-op on old Python. 160 # This is a no-op on old Python.
160 sslcontext.load_default_certs() 161 sslcontext.load_default_certs()
162 caloaded = _canloaddefaultcerts
161 163
162 sslsocket = sslcontext.wrap_socket(sock, server_hostname=serverhostname) 164 sslsocket = sslcontext.wrap_socket(sock, server_hostname=serverhostname)
163 # check if wrap_socket failed silently because socket had been 165 # check if wrap_socket failed silently because socket had been
164 # closed 166 # closed
165 # - see http://bugs.python.org/issue13721 167 # - see http://bugs.python.org/issue13721
166 if not sslsocket.cipher(): 168 if not sslsocket.cipher():
167 raise error.Abort(_('ssl connection failed')) 169 raise error.Abort(_('ssl connection failed'))
170
171 sslsocket._hgcaloaded = caloaded
172
168 return sslsocket 173 return sslsocket
169 174
170 def _verifycert(cert, hostname): 175 def _verifycert(cert, hostname):
171 '''Verify that cert (in socket.getpeercert() format) matches hostname. 176 '''Verify that cert (in socket.getpeercert() format) matches hostname.
172 CRLs is not handled. 177 CRLs is not handled.
278 # FUTURE this can disappear once wrapsocket() is secure by default. 283 # FUTURE this can disappear once wrapsocket() is secure by default.
279 if _canloaddefaultcerts: 284 if _canloaddefaultcerts:
280 kws['cert_reqs'] = ssl.CERT_REQUIRED 285 kws['cert_reqs'] = ssl.CERT_REQUIRED
281 return kws 286 return kws
282 287
283 # This is effectively indicating that no CAs can be loaded because
284 # we can't get here if web.cacerts is set or if we can find
285 # CA certs elsewhere. Using a config option (which is later
286 # consulted by validator.__call__ is not very obvious).
287 # FUTURE fix this
288 ui.setconfig('web', 'cacerts', '!', 'defaultcacerts')
289 return kws 288 return kws
290 289
291 class validator(object): 290 class validator(object):
292 def __init__(self, ui, host): 291 def __init__(self, ui, host):
293 self.ui = ui 292 self.ui = ui
340 'verified (check hostfingerprints or web.cacerts ' 339 'verified (check hostfingerprints or web.cacerts '
341 'config setting)\n') % 340 'config setting)\n') %
342 (host, nicefingerprint)) 341 (host, nicefingerprint))
343 return 342 return
344 343
345 # No pinned fingerprint. Establish trust by looking at the CAs. 344 if not sock._hgcaloaded:
346 cacerts = self.ui.config('web', 'cacerts') 345 if strict:
347 if cacerts != '!': 346 raise error.Abort(_('%s certificate with fingerprint %s not '
348 msg = _verifycert(peercert2, host) 347 'verified') % (host, nicefingerprint),
349 if msg: 348 hint=_('check hostfingerprints or '
350 raise error.Abort(_('%s certificate error: %s') % (host, msg), 349 'web.cacerts config setting'))
351 hint=_('configure hostfingerprint %s or use ' 350 else:
352 '--insecure to connect insecurely') % 351 self.ui.warn(_('warning: %s certificate with fingerprint %s '
353 nicefingerprint) 352 'not verified (check hostfingerprints or '
354 self.ui.debug('%s certificate successfully verified\n' % host) 353 'web.cacerts config setting)\n') %
355 elif strict: 354 (host, nicefingerprint))
356 raise error.Abort(_('%s certificate with fingerprint %s not ' 355
357 'verified') % (host, nicefingerprint), 356 return
358 hint=_('check hostfingerprints or web.cacerts ' 357
359 'config setting')) 358 msg = _verifycert(peercert2, host)
360 else: 359 if msg:
361 self.ui.warn(_('warning: %s certificate with fingerprint %s not ' 360 raise error.Abort(_('%s certificate error: %s') % (host, msg),
362 'verified (check hostfingerprints or web.cacerts ' 361 hint=_('configure hostfingerprint %s or use '
363 'config setting)\n') % 362 '--insecure to connect insecurely') %
364 (host, nicefingerprint)) 363 nicefingerprint)