Mercurial > hg
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 |