comparison mercurial/url.py @ 12592:f2937d6492c5 stable

url: verify correctness of https server certificates (issue2407) Pythons SSL module verifies that certificates received for HTTPS are valid according to the specified cacerts, but it doesn't verify that the certificate is for the host we connect to. We now explicitly verify that the commonName in the received certificate matches the requested hostname and is valid for the time being. This is a minimal patch where we try to fail to the safe side, but we do still rely on Python's SSL functionality and do not try to implement the standards fully and correctly. CRLs and subjectAltName are not handled and proxies haven't been considered. This change might break connections to some sites if cacerts is specified and the certificates (by our definition) isn't correct. The workaround is to disable cacerts which in most cases isn't much worse than it was before with cacerts.
author Mads Kiilerich <mads@kiilerich.com>
date Fri, 01 Oct 2010 00:46:59 +0200
parents ca5fd84d62c6
children 0f83a402faa0 14198926975d
comparison
equal deleted inserted replaced
12576:1c9bb7e00f71 12592:f2937d6492c5
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 import urllib, urllib2, urlparse, httplib, os, re, socket, cStringIO 10 import urllib, urllib2, urlparse, httplib, os, re, socket, cStringIO, time
11 from i18n import _ 11 from i18n import _
12 import keepalive, util 12 import keepalive, util
13 13
14 def _urlunparse(scheme, netloc, path, params, query, fragment, url): 14 def _urlunparse(scheme, netloc, path, params, query, fragment, url):
15 '''Handle cases where urlunparse(urlparse(x://)) doesn't preserve the "//"''' 15 '''Handle cases where urlunparse(urlparse(x://)) doesn't preserve the "//"'''
467 467
468 def _start_transaction(self, h, req): 468 def _start_transaction(self, h, req):
469 _generic_start_transaction(self, h, req) 469 _generic_start_transaction(self, h, req)
470 return keepalive.HTTPHandler._start_transaction(self, h, req) 470 return keepalive.HTTPHandler._start_transaction(self, h, req)
471 471
472 def _verifycert(cert, hostname):
473 '''Verify that cert (in socket.getpeercert() format) matches hostname and is
474 valid at this time. CRLs and subjectAltName are not handled.
475
476 Returns error message if any problems are found and None on success.
477 '''
478 if not cert:
479 return _('no certificate received')
480 notafter = cert.get('notAfter')
481 if notafter and time.time() > ssl.cert_time_to_seconds(notafter):
482 return _('certificate expired %s') % notafter
483 notbefore = cert.get('notBefore')
484 if notbefore and time.time() < ssl.cert_time_to_seconds(notbefore):
485 return _('certificate not valid before %s') % notbefore
486 dnsname = hostname.lower()
487 for s in cert.get('subject', []):
488 key, value = s[0]
489 if key == 'commonName':
490 certname = value.lower()
491 if (certname == dnsname or
492 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1]):
493 return None
494 return _('certificate is for %s') % certname
495 return _('no commonName found in certificate')
496
472 if has_https: 497 if has_https:
473 class BetterHTTPS(httplib.HTTPSConnection): 498 class BetterHTTPS(httplib.HTTPSConnection):
474 send = keepalive.safesend 499 send = keepalive.safesend
475 500
476 def connect(self): 501 def connect(self):
482 if cacerts: 507 if cacerts:
483 sock = _create_connection((self.host, self.port)) 508 sock = _create_connection((self.host, self.port))
484 self.sock = _ssl_wrap_socket(sock, self.key_file, 509 self.sock = _ssl_wrap_socket(sock, self.key_file,
485 self.cert_file, cert_reqs=CERT_REQUIRED, 510 self.cert_file, cert_reqs=CERT_REQUIRED,
486 ca_certs=cacerts) 511 ca_certs=cacerts)
487 self.ui.debug(_('server identity verification succeeded\n')) 512 msg = _verifycert(self.sock.getpeercert(), self.host)
513 if msg:
514 raise util.Abort('%s certificate error: %s' % (self.host, msg))
515 self.ui.debug(_('%s certificate successfully verified\n') %
516 self.host)
488 else: 517 else:
489 httplib.HTTPSConnection.connect(self) 518 httplib.HTTPSConnection.connect(self)
490 519
491 class httpsconnection(BetterHTTPS): 520 class httpsconnection(BetterHTTPS):
492 response_class = keepalive.HTTPResponse 521 response_class = keepalive.HTTPResponse