Mercurial > public > mercurial-scm > hg
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) |