changeset 13315:0d1dca7d2a04

merge with stable
author Mads Kiilerich <mads@kiilerich.com>
date Fri, 28 Jan 2011 03:09:22 +0100
parents 0c493e5ce8e9 (current diff) 8dc488dfcdb4 (diff)
children d119403fd266 d8d478f9ee0f
files doc/hgrc.5.txt mercurial/hg.py mercurial/url.py
diffstat 4 files changed, 68 insertions(+), 7 deletions(-) [+]
line wrap: on
line diff
--- a/doc/hgrc.5.txt	Thu Jan 27 17:22:37 2011 -0600
+++ b/doc/hgrc.5.txt	Fri Jan 28 03:09:22 2011 +0100
@@ -423,6 +423,24 @@
   myfeature = ~/.hgext/myfeature.py
 
 
+``hostfingerprints``
+""""""""""""""""""""
+
+Fingerprints of the certificates of known HTTPS servers.
+A HTTPS connection to a server with a fingerprint configured here will
+only succeed if the servers certificate matches the fingerprint.
+This is very similar to how ssh known hosts works.
+The fingerprint is the SHA-1 hash value of the DER encoded certificate.
+The CA chain and web.cacerts is not used for servers with a fingerprint.
+
+For example::
+
+    [hostfingerprints]
+    hg.intevation.org = 38:76:52:7c:87:26:9a:8f:4a:f8:d3:de:08:45:3b:ea:d6:4b:ee:cc
+
+This feature is only supported when using Python 2.6 or later.
+
+
 ``format``
 """"""""""
 
--- a/mercurial/hg.py	Thu Jan 27 17:22:37 2011 -0600
+++ b/mercurial/hg.py	Fri Jan 28 03:09:22 2011 +0100
@@ -544,7 +544,7 @@
         dst.setconfig('bundle', 'mainreporoot', r)
 
     # copy selected local settings to the remote ui
-    for sect in ('auth', 'http_proxy'):
+    for sect in ('auth', 'hostfingerprints', 'http_proxy'):
         for key, val in src.configitems(sect):
             dst.setconfig(sect, key, val)
     v = src.config('web', 'cacerts')
--- a/mercurial/url.py	Thu Jan 27 17:22:37 2011 -0600
+++ b/mercurial/url.py	Fri Jan 28 03:09:22 2011 +0100
@@ -551,7 +551,8 @@
             else:
                 cacerts = None
 
-            if cacerts:
+            hostfingerprint = self.ui.config('hostfingerprints', self.host)
+            if cacerts and not hostfingerprint:
                 sock = _create_connection((self.host, self.port))
                 self.sock = _ssl_wrap_socket(sock, self.key_file,
                         self.cert_file, cert_reqs=CERT_REQUIRED,
@@ -563,10 +564,33 @@
                 self.ui.debug('%s certificate successfully verified\n' %
                               self.host)
             else:
-                self.ui.warn(_("warning: %s certificate not verified "
-                               "(check web.cacerts config setting)\n") %
-                             self.host)
                 httplib.HTTPSConnection.connect(self)
+                if hasattr(self.sock, 'getpeercert'):
+                    peercert = self.sock.getpeercert(True)
+                    peerfingerprint = util.sha1(peercert).hexdigest()
+                    nicefingerprint = ":".join([peerfingerprint[x:x + 2]
+                        for x in xrange(0, len(peerfingerprint), 2)])
+                    if hostfingerprint:
+                        if peerfingerprint.lower() != \
+                                hostfingerprint.replace(':', '').lower():
+                            raise util.Abort(_('invalid certificate for %s '
+                                               'with fingerprint %s') %
+                                             (self.host, nicefingerprint))
+                        self.ui.debug('%s certificate matched fingerprint %s\n' %
+                                      (self.host, nicefingerprint))
+                    else:
+                        self.ui.warn(_('warning: %s certificate '
+                                       'with fingerprint %s not verified '
+                                       '(check hostfingerprints or web.cacerts '
+                                       'config setting)\n') %
+                                     (self.host, nicefingerprint))
+                else: # python 2.5 ?
+                    if hostfingerprint:
+                        raise util.Abort(_('no certificate for %s '
+                                           'with fingerprint') % self.host)
+                    self.ui.warn(_('warning: %s certificate not verified '
+                                   '(check web.cacerts config setting)\n') %
+                                 self.host)
 
     class httpsconnection(BetterHTTPS):
         response_class = keepalive.HTTPResponse
--- a/tests/test-https.t	Thu Jan 27 17:22:37 2011 -0600
+++ b/tests/test-https.t	Fri Jan 28 03:09:22 2011 +0100
@@ -106,7 +106,7 @@
 clone via pull
 
   $ hg clone https://localhost:$HGPORT/ copy-pull
-  warning: localhost certificate not verified (check web.cacerts config setting)
+  warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
   requesting all changes
   adding changesets
   adding manifests
@@ -132,7 +132,7 @@
   $ echo '[hooks]' >> .hg/hgrc
   $ echo "changegroup = python '$TESTDIR'/printenv.py changegroup" >> .hg/hgrc
   $ hg pull
-  warning: localhost certificate not verified (check web.cacerts config setting)
+  warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
   changegroup hook: HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_URL=https://localhost:$HGPORT/ 
   pulling from https://localhost:$HGPORT/
   searching for changes
@@ -188,3 +188,22 @@
   $ hg -R copy-pull pull --config web.cacerts=pub-expired.pem https://localhost:$HGPORT2/
   abort: error: *:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (glob)
   [255]
+
+Fingerprints
+
+  $ echo "[hostfingerprints]" >> copy-pull/.hg/hgrc
+  $ echo "localhost = 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca" >> copy-pull/.hg/hgrc
+  $ echo "127.0.0.1 = 914f1aff87249c09b6859b88b1906d30756491ca" >> copy-pull/.hg/hgrc
+
+- works without cacerts
+  $ hg -R copy-pull id https://localhost:$HGPORT/ --config web.cacerts=
+  5fed3813f7f5
+
+- fails when cert doesn't match hostname (port is ignored)
+  $ hg -R copy-pull id https://localhost:$HGPORT1/
+  abort: invalid certificate for localhost with fingerprint 28:ff:71:bf:65:31:14:23:ad:62:92:b4:0e:31:99:18:fc:83:e3:9b
+  [255]
+
+- ignores that certificate doesn't match hostname
+  $ hg -R copy-pull id https://127.0.0.1:$HGPORT/
+  5fed3813f7f5