Moderate severityNVD Advisory· Published Oct 29, 2019· Updated Aug 7, 2024
CVE-2010-4237
CVE-2010-4237
Description
Mercurial before 1.6.4 fails to verify the Common Name field of SSL certificates which allows remote attackers who acquire a certificate signed by a Certificate Authority to perform a man-in-the-middle attack.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
mercurialPyPI | < 1.6.4 | 1.6.4 |
Affected products
1Patches
24ea63fb25ceeurl: validity (notBefore/notAfter) is checked by OpenSSL (issue2407)
2 files changed · +4 −24
mercurial/url.py+3 −9 modified@@ -7,7 +7,7 @@ # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. -import urllib, urllib2, urlparse, httplib, os, re, socket, cStringIO, time +import urllib, urllib2, urlparse, httplib, os, re, socket, cStringIO import __builtin__ from i18n import _ import keepalive, util @@ -487,19 +487,13 @@ def _start_transaction(self, h, req): return keepalive.HTTPHandler._start_transaction(self, h, req) def _verifycert(cert, hostname): - '''Verify that cert (in socket.getpeercert() format) matches hostname and is - valid at this time. CRLs and subjectAltName are not handled. + '''Verify that cert (in socket.getpeercert() format) matches hostname. + CRLs and subjectAltName are not handled. Returns error message if any problems are found and None on success. ''' if not cert: return _('no certificate received') - notafter = cert.get('notAfter') - if notafter and time.time() > ssl.cert_time_to_seconds(notafter): - return _('certificate expired %s') % notafter - notbefore = cert.get('notBefore') - if notbefore and time.time() < ssl.cert_time_to_seconds(notbefore): - return _('certificate not valid before %s') % notbefore dnsname = hostname.lower() for s in cert.get('subject', []): key, value = s[0]
tests/test-url.py+1 −15 modified@@ -1,9 +1,5 @@ #!/usr/bin/env python import sys -try: - import ssl -except ImportError: - sys.exit(80) def check(a, b): if a != b: @@ -36,17 +32,7 @@ def cert(cn): check(_verifycert(cert('*o'), 'foo'), 'certificate is for *o') -import time -lastyear = time.gmtime().tm_year - 1 -nextyear = time.gmtime().tm_year + 1 -check(_verifycert({'notAfter': 'May 9 00:00:00 %s GMT' % lastyear}, - 'example.com'), - 'certificate expired May 9 00:00:00 %s GMT' % lastyear) -check(_verifycert({'notBefore': 'May 9 00:00:00 %s GMT' % nextyear}, - 'example.com'), - 'certificate not valid before May 9 00:00:00 %s GMT' % nextyear) -check(_verifycert({'notAfter': 'Sep 29 15:29:48 %s GMT' % nextyear, - 'subject': ()}, +check(_verifycert({'subject': ()}, 'example.com'), 'no commonName found in certificate') check(_verifycert(None, 'example.com'),
89baabf4fb7aurl: verify correctness of https server certificates (issue2407)
2 files changed · +72 −2
mercurial/url.py+31 −2 modified@@ -7,7 +7,7 @@ # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. -import urllib, urllib2, urlparse, httplib, os, re, socket, cStringIO +import urllib, urllib2, urlparse, httplib, os, re, socket, cStringIO, time from i18n import _ import keepalive, util @@ -469,6 +469,31 @@ def _start_transaction(self, h, req): _generic_start_transaction(self, h, req) return keepalive.HTTPHandler._start_transaction(self, h, req) +def _verifycert(cert, hostname): + '''Verify that cert (in socket.getpeercert() format) matches hostname and is + valid at this time. CRLs and subjectAltName are not handled. + + Returns error message if any problems are found and None on success. + ''' + if not cert: + return _('no certificate received') + notafter = cert.get('notAfter') + if notafter and time.time() > ssl.cert_time_to_seconds(notafter): + return _('certificate expired %s') % notafter + notbefore = cert.get('notBefore') + if notbefore and time.time() < ssl.cert_time_to_seconds(notbefore): + return _('certificate not valid before %s') % notbefore + dnsname = hostname.lower() + for s in cert.get('subject', []): + key, value = s[0] + if key == 'commonName': + certname = value.lower() + if (certname == dnsname or + '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1]): + return None + return _('certificate is for %s') % certname + return _('no commonName found in certificate') + if has_https: class BetterHTTPS(httplib.HTTPSConnection): send = keepalive.safesend @@ -484,7 +509,11 @@ def connect(self): self.sock = _ssl_wrap_socket(sock, self.key_file, self.cert_file, cert_reqs=CERT_REQUIRED, ca_certs=cacerts) - self.ui.debug(_('server identity verification succeeded\n')) + msg = _verifycert(self.sock.getpeercert(), self.host) + if msg: + raise util.Abort('%s certificate error: %s' % (self.host, msg)) + self.ui.debug(_('%s certificate successfully verified\n') % + self.host) else: httplib.HTTPSConnection.connect(self)
tests/test-url.py+41 −0 added@@ -0,0 +1,41 @@ +#!/usr/bin/env python + +def check(a, b): + if a != b: + print (a, b) + +from mercurial.url import _verifycert + +# Test non-wildcard certificates +check(_verifycert({'subject': ((('commonName', 'example.com'),),)}, 'example.com'), + None) +check(_verifycert({'subject': ((('commonName', 'example.com'),),)}, 'www.example.com'), + 'certificate is for example.com') +check(_verifycert({'subject': ((('commonName', 'www.example.com'),),)}, 'example.com'), + 'certificate is for www.example.com') + +# Test wildcard certificates +check(_verifycert({'subject': ((('commonName', '*.example.com'),),)}, 'www.example.com'), + None) +check(_verifycert({'subject': ((('commonName', '*.example.com'),),)}, 'example.com'), + 'certificate is for *.example.com') +check(_verifycert({'subject': ((('commonName', '*.example.com'),),)}, 'w.w.example.com'), + 'certificate is for *.example.com') + +# Avoid some pitfalls +check(_verifycert({'subject': ((('commonName', '*.foo'),),)}, 'foo'), + 'certificate is for *.foo') +check(_verifycert({'subject': ((('commonName', '*o'),),)}, 'foo'), + 'certificate is for *o') + +import time +lastyear = time.gmtime().tm_year - 1 +nextyear = time.gmtime().tm_year + 1 +check(_verifycert({'notAfter': 'May 9 00:00:00 %s GMT' % lastyear}, 'example.com'), + 'certificate expired May 9 00:00:00 %s GMT' % lastyear) +check(_verifycert({'notBefore': 'May 9 00:00:00 %s GMT' % nextyear}, 'example.com'), + 'certificate not valid before May 9 00:00:00 %s GMT' % nextyear) +check(_verifycert({'notAfter': 'Sep 29 15:29:48 %s GMT' % nextyear, 'subject': ()}, 'example.com'), + 'no commonName found in certificate') +check(_verifycert(None, 'example.com'), + 'no certificate received')
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
10- github.com/advisories/GHSA-7gf7-7wx4-mxmwghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2010-4237ghsaADVISORY
- bugs.debian.org/cgi-bin/bugreport.cgighsax_refsource_MISCWEB
- bugzilla.redhat.com/show_bug.cgighsax_refsource_CONFIRMWEB
- bz.mercurial-scm.org/show_bug.cgighsax_refsource_CONFIRMWEB
- github.com/dscho/hg/commit/4ea63fb25ceeeaaa4cd1026f733b7ea7672c30b3ghsaWEB
- github.com/dscho/hg/commit/89baabf4fb7abf30ef6fdcf3d455a7893e5cc145ghsaWEB
- repo.mercurial-scm.org/hg/rev/6ab4a7d3c179ghsaWEB
- repo.mercurial-scm.org/hg/rev/f2937d6492c5ghsaWEB
- security-tracker.debian.org/tracker/CVE-2010-4237ghsax_refsource_MISCWEB
News mentions
0No linked articles in our index yet.